From cd4d149240196d49988ffb7b5d649560bc19d9b0 Mon Sep 17 00:00:00 2001 From: Garvit S Rathore Date: Wed, 15 Oct 2025 23:37:34 +0530 Subject: [PATCH 01/59] Add --- .../__pycache__/backend.cpython-310.pyc | Bin 0 -> 3290 bytes .../__pycache__/backend.cpython-311.pyc | Bin 0 -> 5297 bytes .../__pycache__/backend.cpython-313.pyc | Bin 0 -> 4798 bytes .../__pycache__/common.cpython-310.pyc | Bin 0 -> 1918 bytes .../__pycache__/common.cpython-311.pyc | Bin 0 -> 2185 bytes .../__pycache__/common.cpython-313.pyc | Bin 0 -> 2137 bytes .../__pycache__/input_dock.cpython-310.pyc | Bin 0 -> 9447 bytes .../__pycache__/input_dock.cpython-311.pyc | Bin 0 -> 18172 bytes .../__pycache__/input_dock.cpython-313.pyc | Bin 0 -> 16236 bytes src/osbridge/backend.py | 92 +++++ src/osbridge/common.py | 57 +++ src/osbridge/input_dock.py | 363 ++++++++++++++++++ src/osbridge/resources/resources.qrc | 0 src/osbridge/template_page.py | 184 +++++++++ 14 files changed, 696 insertions(+) create mode 100644 src/osbridge/__pycache__/backend.cpython-310.pyc create mode 100644 src/osbridge/__pycache__/backend.cpython-311.pyc create mode 100644 src/osbridge/__pycache__/backend.cpython-313.pyc create mode 100644 src/osbridge/__pycache__/common.cpython-310.pyc create mode 100644 src/osbridge/__pycache__/common.cpython-311.pyc create mode 100644 src/osbridge/__pycache__/common.cpython-313.pyc create mode 100644 src/osbridge/__pycache__/input_dock.cpython-310.pyc create mode 100644 src/osbridge/__pycache__/input_dock.cpython-311.pyc create mode 100644 src/osbridge/__pycache__/input_dock.cpython-313.pyc create mode 100644 src/osbridge/backend.py create mode 100644 src/osbridge/common.py create mode 100644 src/osbridge/input_dock.py create mode 100644 src/osbridge/resources/resources.qrc create mode 100644 src/osbridge/template_page.py diff --git a/src/osbridge/__pycache__/backend.cpython-310.pyc b/src/osbridge/__pycache__/backend.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2e5ebc2fcf5ff28ff1ef6c8deb24c0bfb6c92743 GIT binary patch literal 3290 zcmb7G&2!U66qjV#mcL><36vHnqO=s3A(Vzfp_IX~o#5h+Ozbeh;~9;jl^soF$4nk=PSOY)iJ(6}AKtBl0VO$SImPyq808>%wzgSsZ8yr@nx7cCWMZ4W95JaibTrCzHle-Mu)9BD%$<$C7JZqT_TaVPrC_Ol z9vFA~yVUe(e{?`62mQ8VRz2&f*{_=Pkh$aj%)dVMjJ`|vxwHMy!L{*`uV|X(SWMHB z$UgxhAtuDMI6bqlLu)g|6W-$vaPMVkF!WVyl|(NNJrAvNs)4|$@-u|mgFZ6li~bJF4}}*Z6PUi5Jpy@t4?h=~s+Nk;yDRM`9d_ zA;J$DL&^EJIy}g?33VEnj>kBoM9hFkPJsMp|wvU*0LPQ@;PVO zId-0X8ZyXnB*&2pOa*QiLu82~OC0%(U4l8Ehe)0y`EZ>tBI^`5Qs8qgvoB%Jm2ggx zBSl1>ix42n^zx3$CZ40(7GPC zth0mb%)M@n^r5*nc7}ek;@XpuL;d)MsgLM1S9jRk8Ksh+#zWU^S9cpV9+iHk`((Qo zj`#_EJcdl*=lHSg*1L_GR%!ZauEV>&f(9z>R?E-L&u`G>N|knVqrK7I^D~djjon%& zP)(s~y;g|>n=x6s+$?zXl%wThpQ7;NA`$_Vld+4r-Ep=uR@!d!EsR&L@5$D5Et zcIxFuB(=^oi zL%9hB+qaCuCj>haLIH`CgI_@*|nrciab4S)x z$322EraGoc;Pyn7eVtl{%1p{yfyeK<2MRM2CV@K$j{hbs{f1u#(o|h!zo`6S4+v%% zX?iR4pyfIyaCTgk9Kp2%+>8U=8JhHelU|AJq81n-KLT&>9*F6sMb_`oHqgV_58GJy zRfd-#gR9^rUI-F65Std8u5qZ^?r>;&Dut9eo~}0LJ#{ixuZ%#W1Q)I!AA6R=N)nG= zKXzyyEk;`&ENCtz`Y^EAf(7~Qo0X;)7O5}jo}fL881F7vfxTYlRNV=~L>>fm@bPDi zIY0aL+Fo?oPMOz-_bUK<#o*HqnprXNvttWyf#9itTE z({hH#Sk9ke{L9frx+{Z;V`wlL35T{H&q<&7{O{0yZ`>Z=$MvdexT7(|t;6UC4pDIk zpLCsQZwOK(Cm!*FJS^rOn literal 0 HcmV?d00001 diff --git a/src/osbridge/__pycache__/backend.cpython-311.pyc b/src/osbridge/__pycache__/backend.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..aa6668d6bd0391b773225616a51a45283e3346fc GIT binary patch literal 5297 zcmbtYO>7j&74Dutx5x8igTtR;X$X)V77Q$gY|z5S_6*oCo-v*=3tlI^?dkR~VWxZ1 zU9-p|h)}c#ANG*TioGC3k-XW}3dxt1d_nRh5|>C#WGhxnk&q%Ea*898%bxOH_0P0> zX2>Ds>E~BfufF$Fuez%GkMVevf%4mL{&{~V#xQ@!ic>;1^JWB?r;Nl%Y?Z08b1aK| zpc*JL0pV~ zBQtZX#LNXG761aDGjkyXp9@PNpb;qyv`2~n<)j{GlcGRlQXi;|naMG+ z^gvdmygsH?q_W(6uTZJgtChuyEb$9gAHS$-{Pl{$Pgg}E^W&8iy_g z;dyff$Ww*@b;=_~a3v)cYOedVhNEba7_cdjyPgV}1Gh5wg!E)#TDU%$o6e3FGiIcw zN{y;)#wA&=lodfIB5CO6pj};P5TZJ*-?C;%m#d39>g9j9GJa)#R+lw>{)VW1Rw46g z^`TN#MMl$uVKuM>ZLtfXE6S?4Xu+;%K|dNn=j=i-EDnz@I{Y4o z1B)$tLtQn5+B6J$Mzds}rxvo&!d=nAZDRsFrboNtM%!@Dx5ktAJ$xtJzOJ}^eq8V#HyOKh^gh-VE#^lfmmMTn+aGv% ze23eGeCTlQ3W|5t5O33Ptu>nb+{1hGVXW0|=R=~ahD4i&bZa!Z?%};YOtji<4au$= zl70;k^P8>l9b?|#6|LWoMzS8PoQLDg?CZdhht@!oXCZZRWby$)v#Gfp@3;$$(K z5yq#?D3zgAlS6>v6fDtR6rG$Jy^*o8ES@41 zW~Ym}8->icne+f=v7xtQonWK|GlqRAn#PWPS8^;fI)yE?e+o_?Gn1oP&!NO^O4|0g z4GE8r*9%#b+E|##jLw(|7v8GJEdMiii|82@9KDNrT7p(R>A@V!XNzW-B3V(VeQEQ+ zt~d$fvxQ=QYH}`w8PZs@D=R~)~=;rZ%yV$YXTD_6cDtHrfzM*QY- zpd~f-iaWZ&9W}V4+lhV8;wz88f3QCC@*N|7x3koLB253muTHE)jWZv;Rq1wO=$Y`m z_Tt|9(yObtH?H0`3LhKy%0~QtXW;F`q0Pf*=zxc>uI4wc=8fC4#>cV|Uwq4ev(F#D zP+t;bwq(Sm&T{B|Zsq1`XjNLv8Sz`4rM46MpS|<^@G4m=u77023;&N4YRs?9tqua( zFO7J%3s!RQ%U^9Czw%;YU3!@_5}$OQyzS(^XR(#q_j7NLHcD-ty7s5&%d|1`sgV#n zqrFb<-TcwXmFDWm+Ta>wOs=!oc5-O*$nfU*i9eq+J`oI2H4^pClCP6P1~=4xf_HBb zc$CKzJl-9eNa$vdle|jL{4i;!$wmrzHiVQX@DlaX; zjR$Ni-4~Uztamm#r`-l!ZFi=Rkd7}HC~6Uy51bjx;4I0=ijY?L<;VJX1K0Z6p{*9 zZ_VHXc}Yk0Ja4_#k}zCQbxAB+aLuEBuY3Y{?9lfEdBSXSaU*f!6?bxjJ83w-b`LwJ z*dFSG+8}tcuwJS(_Ku~Y#j04|Woq0KcHY28{D$u8o&gKB0unqemb?-7*<&Znd(<8< z1dlS9*k^gM`cPcb`MRj%%~vP9}NUECJ4W7h*i6%M-U{nBnTSrhZ^p!S{@0Vn_1*2?k|x06v+Y-x&YB@ znxGoKmnaDVqyGWO6ZdZ`bjb)^+6e{2r*{~aMA`7M9mXY*OX0&ij7tAO`!UAVQ;SsN^_1HorCXDd*Q2?s*!4CVOW!OwV{<{UBe}BMqmFUiiq A-~a#s literal 0 HcmV?d00001 diff --git a/src/osbridge/__pycache__/backend.cpython-313.pyc b/src/osbridge/__pycache__/backend.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..75cab98e32b5606bfe9a8b922145790d091209e4 GIT binary patch literal 4798 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 literal 0 HcmV?d00001 diff --git a/src/osbridge/__pycache__/common.cpython-310.pyc b/src/osbridge/__pycache__/common.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dd00025b801058ef22b32265d2019ff2bf625d7e GIT binary patch literal 1918 zcmZ`)*>d7m5M?!D$Ln}@&2AoM3NY9vl}f@Qd#GV63ldKkRg~b~V3oB{l9*ss9`hyh zl>A0MgV%gO-t&~ZNEknisC#uz-|psgs|98{od}St^5?(Tax4(|hmy@JOvq1U*?*G_ z1t5Te5JVvep)iC|1R^L3QM3hHC=m(Ia?<9>a6=1Vr={J)4E# z#VmlHe+|M*B%&9Dzd|q3D{_DB@i*T6EqaIEptpYn;T?J{2l7X&E!{*54X>h(>S!2* zsi{xQiMfPbAC0FA+c9<3#Ry^!UOMzNs_M!NueLfY8d43@F!0n-zo^dbD%zrlZl+;s z3*8W9dSDuEs*V-iXDgwjIM`AZ%}vVJSh2)&)0$#8Sz8jDN78jubHt{q=$QDraYhHX zbL=JsxI=wNlZM%Etsv&ZtuZw*B{v8(kmO00kqhi}lwM_+yP3$PD zX1lo!E#lElH}B>)1aV7>tMVe!zC7_r*Ym3gl?PF-EX_su$unz-%-22e8uY8C7&H8y zwdXfHad$mC!E0lF%gsuqN_^hQLAqf8VVi&Jl;OhbGxTG&}w zhArBPKG!gFf*5a;osYPgVM9DFRF-s=%7uHpSUtPXE1Z`q4Mo&Jv$h89zHdTb8P&WvO(+_~{A3C#TC|_0)=z(!2TKD*JP-J(T2er#6r- zTeWsR1PM}?hQ|gOW{A$s`C|T8z!Hf6e?LyY8x3u2*(3ehUR;lQ25wsF7d&cW`^GWn zqn=&2R5ZgQ+nSC{d%_8ML3PvkFuz^Jys{|v1)_~wN!;N02-mCYnR)_cKxG|Imy-o|H<|@U5q^kk-<0)F5+Jyv( zPts>EH3$?pdfhI>DdG-k3guyc(7lkQ2Bdu2TrFxQ^HJ4V|E?T=(VYs+}co=Y8`61 z%emH=*~ob=w;{6*;==DZSD)SRulL#m-Y{nLD&P*JFxDEzXmz=bKwi$Bnsw1hZ{}!0 z7WwxIfZ$o0knj-gnU85=e@^`*Y*5%#ZftRqYbAWo|0#Y55@z?wMh9#C39r$ejiPVa OnU@guhg1k=!v6!@H4Ux+ literal 0 HcmV?d00001 diff --git a/src/osbridge/__pycache__/common.cpython-311.pyc b/src/osbridge/__pycache__/common.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3d8525e5a208e1595d1a2fb6dab384b020c53612 GIT binary patch literal 2185 zcmZ`)OK;mo5T;P~0Wk~UzX<+I;J=I)P!cbq6}*IQ;bn9iC($Zi zL3i*il)|^sUA&4!d-R2`FqtDST&^pVg#uNFI-<)vNTSGnx3Bc66ky z4hC{}r2M3e-V$$}8jWaK?OpT-Vw)P&fe|UmQr|@+(3D29rbs;_+7`is72g7KS&#~8wJ@+#IDMT;N9}X&3->k=4g4u zYgfkGMYzeM(-N7lvE&Ts$DTAelOM71ypd0hgu{bACDT4bsOIt+akD0i+u6*EbkX3e zTN#Jg&ZH@H?F9zGjk)J->$YRf?=oxNfjjw3n#>k^JrXKUcbaXMrVP$R+N?2O(-gLe z;=ZN~8F$ciF9W?3RG)?Q=_!bJ+|O_=&Y4}CgM&Hq-qWVs>!1w7D}5 zj;GIf+*G^gB9=zdsiezd*EweDKzt->^5{Yv=%Oz5hdr4&5x+e;Jw0bckILd!CO@Vt zx1IR{XA65@;=GM>`Ry@X*_~bDrTbwgDv5>c_INv67;o)SM;o)mdj*E~w)V!kLT;SP z?J&H%1NzSHI9u4&!q8RPLxdlSjiYM&q*?4#pVy0x^b83>TRr(?0Oy-{hwAWRsMDc| z;^#v7o8u#0*7W0jNrOvuTv6W)dT>(pst3%)lY2zx3#Yh9uMD=pfep^XjmA(ri_Z8dVZvpi}RF&NIE-YL;51)=QH70-ltQ+MQOjj;a+B zv4g5FJJiokQEwLaA>Fg;D>rq3>gCqK0SQvgcHpVnK053i?6<3B5^-T=irhKdQSGPI z;&WRqzJ%!$FO8@WKYc+NwzB^milWS619qwN^cjgWw|e{Vq*gy*Ea58@vQ|&ZO~NW+ z6su8~29#S3T1;$8l&ZxhWv+8hYmDB=S!*{)bQ)wAevR$wg&Y2*R-NK%?yxk=1@cqGQNeI`LKzt57 zvHxqn)xHPlfu$!vUd?(uo{!O`N!a2SCLt?!YrmOv!dnfrZkhm5gJGySa@{SL~WH%#_}kVyRU ti5V85%IK<@%9;EYq=+Uik4=6Zv_$gPug&?5iE!l$c%oO_{|Gj;e*txPkKF(O literal 0 HcmV?d00001 diff --git a/src/osbridge/__pycache__/common.cpython-313.pyc b/src/osbridge/__pycache__/common.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ba16673cb8db41e98dab8ebe5a17c8da6c4e38b5 GIT binary patch literal 2137 zcmY*a-EJC36kcre&$3`_j2(xp9Va$wlK?jUiK3+ZIw)YA3~rnj$y&qK-WpgmixaX} z`v|^?)JJGvqfda8o7Jcnk@^6Q^f%`|w_~@dNw)5IsZ>mbhPCc>QcGwN5-e zwR72(iF>FF;pJ z@?IH!Bfxf=z3lZxL>+2h4;5so=OE&%OG6neQdbY6b6LV-Ma6Ad59UX}*?=#o z-Jw`gq@E11x}U-3$APQ|n7NOoj;srZT_`0s&z1H?U)D5zuA=lsi*$6s&(Gi3;vrTC zG9D_jriW^()DcVaP*S>@9=WAO+_*SM>5*H4EGA^BvNL!`p19HLrN|7GN8FY&-V)&y z4^JerSUqkJ=$Ee4Ka(G^@qCa^^_b0rJt0#bT_|UBX>p?>i(8rW+f-iX%bRJN*h;4; zbo_e^LKq9rS>7$%o7-XDoDH{g=@gmHce^A|oSZZ}EUfg;MB1!ATfquDL~$Q01I8V+ zosWTj6V#rDbzOn&6L$ok#nWSslV&&$F6Pv0*Ba%fZ*em==VqLT<9>!r>sf=GH4Zv{ zTvyu{Vn-TEr;;X%ZTpg`eescq<>4jnYoaFg23?s25wA5kEj6QukBZ`EIya&#yOsV1 zXZCi##W@RSb6X?2GTS@EO;5urR1o(vTcfSa-e_}|23nXE-rZw(cXM}?-OG-$*=>e* zw!z-s8D;i%a1eS*xrp#%zIIebC-r=@{HB_(rKU&#+Um+DeYoAkJx~Xi1C5SR6v>PB z)7FtDW3AOY*DlXnjlNvM%6qw0lC_JWI%qYt0#-U_a!bSQma3gv0KV9s+J8LwNc{Gw zI;VI6N@iebbThaw-ssi4&)y^v-j025O@Hj_!)O03q<{9W{3RsEzN8uCjp{Wdf&7Ei z1_?Ib9hOh(jnYxAOacrvt4*+ZW)~avLZi?)CW&w0N%06Z8};f>5N#)}vjk8Kr z>-l{s_p1EPDea?nv2k!f{M51ncxs}f!{)(0Di=w}fu#v@25V)Zm*xB$%gi6cxQUx) zl!%wUK!#=PzX4H{Ijq64G+(|VQ5IH3hbNWl0b>bYqL8(EQmhlEgi%bRFi9vjYE(>Q zLKMpRI%RHZPBeyZ)vTZz2~UzN;WyY)FWibRG-^$2V2rdoumYel)(=CpyU49XtDPG) zlct0A-J}iS7#uk9*-VK9Y>Up!v1wuFK08I&J^O>h2}s9rR^yrV*YX_y0MG?X`xmya z(=M0mOE_)>O@7V@n2{xeH}9m!T*4F*Mu?GLJsboC%;2KoGea@MZ$?Cj67GUuScEOK z2prpd3x?Ya%o|=a5Qj|=jSytde>di$W@H_bB1zb0R}I08t&F*dIhUla=shEBM(;b@ ztO$O78Gcfjr@s-0Ew;+S*B~4!MmEN&=i~4TBS>a-2o6QBo{fVd=nF58li4x93CZEu aRcp+zf)z{r_WgKv-4Oos_*`K&mHz>wXn(o@ literal 0 HcmV?d00001 diff --git a/src/osbridge/__pycache__/input_dock.cpython-310.pyc b/src/osbridge/__pycache__/input_dock.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..11c970181ff83fc6c4e4b4614c1b744c6d439712 GIT binary patch literal 9447 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 literal 0 HcmV?d00001 diff --git a/src/osbridge/__pycache__/input_dock.cpython-313.pyc b/src/osbridge/__pycache__/input_dock.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c7d21a2ce0c04be4cee9970a719740e7535d16f4 GIT binary patch literal 16236 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

$;tx&8-`d-@CB$W;6GR*a18V2<^Rte^Pq z0Lwx*q|IcO?S1U62IgcVe1`1Vcc2P7ARWFpy~Eb!(ebETWV?a486S*20FE|7@?>Wq z*x#Q6^I6E$9noh#(3&_*!FB~+25)M+D&JOZrmFLtT)+tZKBDQchj=$DHIx4GfV7K^#|kKC(e=AJzUW8&gMT7Z{hS?X3w@?5?Z z1gFuXy>n?Pe%NUOQCv#b3F2jm5R6Vm84K*9Lo-y$F(rz(S geOY0J$m=(_zPxI18rWw|7fptq4eK8me3HTc7e_~gA^-pY literal 0 HcmV?d00001 diff --git a/src/osbridge/backend.py b/src/osbridge/backend.py new file mode 100644 index 00000000..cc0ea892 --- /dev/null +++ b/src/osbridge/backend.py @@ -0,0 +1,92 @@ +from importlib.resources import files +from common import * + +class BackendOsBridge: + """Simplified backend for Fin Plate Connection""" + + def __init__(self): + self.module = KEY_DISP_FINPLATE + self.design_status = False + self.design_button_status = False + + def module_name(self): + return KEY_DISP_FINPLATE + + def input_values(self): + """Return list of input fields for the UI""" + options_list = [] + + t16 = (KEY_MODULE, KEY_DISP_FINPLATE, TYPE_MODULE, None, True, 'No Validator') + options_list.append(t16) + + t1 = (None, DISP_TITLE_CM, TYPE_TITLE, None, True, 'No Validator') + options_list.append(t1) + + t2 = (KEY_CONN, KEY_DISP_CONN, TYPE_COMBOBOX, VALUES_CONN, True, 'No Validator') + options_list.append(t2) + + # Mock image path - you'll need to provide an actual image + t15 = (KEY_IMAGE, None, TYPE_IMAGE, "path/to/image.png", True, 'No Validator') + options_list.append(t15) + + t3 = (KEY_SUPTNGSEC, KEY_DISP_COLSEC, TYPE_COMBOBOX, connectdb("Columns"), True, 'No Validator') + options_list.append(t3) + + t4 = (KEY_SUPTDSEC, KEY_DISP_BEAMSEC, TYPE_COMBOBOX, connectdb("Beams"), True, 'No Validator') + options_list.append(t4) + + t5 = (KEY_MATERIAL, KEY_DISP_MATERIAL, TYPE_COMBOBOX, VALUES_MATERIAL, True, 'No Validator') + options_list.append(t5) + + t6 = (None, DISP_TITLE_FSL, TYPE_TITLE, None, True, 'No Validator') + options_list.append(t6) + + t7 = (KEY_SHEAR, KEY_DISP_SHEAR, TYPE_TEXTBOX, None, True, 'Int Validator') + options_list.append(t7) + + t8 = (KEY_AXIAL, KEY_DISP_AXIAL, TYPE_TEXTBOX, None, True, 'Int Validator') + options_list.append(t8) + + t9 = (None, DISP_TITLE_BOLT, TYPE_TITLE, None, True, 'No Validator') + options_list.append(t9) + + t10 = (KEY_D, KEY_DISP_D, TYPE_COMBOBOX_CUSTOMIZED, VALUES_D, True, 'No Validator') + options_list.append(t10) + + t11 = (KEY_TYP, KEY_DISP_TYP, TYPE_COMBOBOX, VALUES_TYP, True, 'No Validator') + options_list.append(t11) + + t12 = (KEY_GRD, KEY_DISP_GRD, TYPE_COMBOBOX_CUSTOMIZED, VALUES_GRD, True, 'No Validator') + options_list.append(t12) + + t13 = (None, DISP_TITLE_PLATE, TYPE_TITLE, None, True, 'No Validator') + options_list.append(t13) + + t14 = (KEY_PLATETHK, KEY_DISP_PLATETHK, TYPE_COMBOBOX_CUSTOMIZED, VALUES_PLATETHK, True, 'No Validator') + options_list.append(t14) + + return options_list + + def customized_input(self): + """Return empty list for now - customization not needed for basic test""" + return [] + + def input_value_changed(self): + """Return None - no dynamic changes needed for basic test""" + return None + + def set_osdaglogger(self, key): + """Mock logger setup""" + print("Logger set up (mock)") + + def output_values(self, flag): + """Mock output values""" + return [] + + def func_for_validation(self, design_inputs): + """Mock validation - always passes for testing""" + return None + + def get_3d_components(self): + """Mock 3D components""" + return [] \ No newline at end of file diff --git a/src/osbridge/common.py b/src/osbridge/common.py new file mode 100644 index 00000000..c189bdb8 --- /dev/null +++ b/src/osbridge/common.py @@ -0,0 +1,57 @@ +# Constants for input types +TYPE_MODULE = "module" +TYPE_TITLE = "title" +TYPE_COMBOBOX = "combobox" +TYPE_COMBOBOX_CUSTOMIZED = "combobox_customized" +TYPE_TEXTBOX = "textbox" +TYPE_IMAGE = "image" + +# Keys for inputs +KEY_MODULE = "Module" +KEY_CONN = "Connectivity" +KEY_IMAGE = "Image" +KEY_SUPTNGSEC = "Column Section" +KEY_SUPTDSEC = "Beam Section" +KEY_MATERIAL = "Material" +KEY_SHEAR = "Shear Force" +KEY_AXIAL = "Axial Force" +KEY_D = "Bolt Diameter" +KEY_TYP = "Bolt Type" +KEY_GRD = "Bolt Grade" +KEY_PLATETHK = "Plate Thickness" + +# Display names +KEY_DISP_FINPLATE = "Fin Plate Connection" +DISP_TITLE_CM = "Connection Properties" +KEY_DISP_CONN = "Connectivity" +KEY_DISP_COLSEC = "Column Section" +KEY_DISP_BEAMSEC = "Beam Section" +KEY_DISP_MATERIAL = "Material" +DISP_TITLE_FSL = "Load Details" +KEY_DISP_SHEAR = "Shear Force (kN)" +KEY_DISP_AXIAL = "Axial Force (kN)" +DISP_TITLE_BOLT = "Bolt Details" +KEY_DISP_D = "Diameter (mm)" +KEY_DISP_TYP = "Type" +KEY_DISP_GRD = "Grade" +DISP_TITLE_PLATE = "Plate Details" +KEY_DISP_PLATETHK = "Thickness (mm)" + +# Sample values +VALUES_CONN = ["Column flange-Beam web", "Column web-Beam web"] +VALUES_MATERIAL = ["E250 (Fe 410W)A", "E300 (Fe 440)", "E350 (Fe 490)", "E410 (Fe 540)", "E450 (Fe 570)", "E550 (Fe 650)"] +VALUES_D = ["All", "Customized"] +VALUES_TYP = ["Bearing Bolt", "Friction Grip Bolt"] +VALUES_GRD = ["All", "Customized"] +VALUES_PLATETHK = ["All", "Customized"] + + +def connectdb(table_name, popup=None): + """Mock database connection - returns sample data""" + if table_name == "Columns": + return ["UC 305x305x240", "UC 305x305x198", "UC 305x305x158", "UC 254x254x167"] + elif table_name == "Beams": + return ["UB 914x419x388", "UB 914x305x289", "UB 838x292x226", "UB 762x267x197"] + elif table_name == "Material": + return VALUES_MATERIAL + return [] \ No newline at end of file diff --git a/src/osbridge/input_dock.py b/src/osbridge/input_dock.py new file mode 100644 index 00000000..5c4c8397 --- /dev/null +++ b/src/osbridge/input_dock.py @@ -0,0 +1,363 @@ +import sys +from PySide6.QtWidgets import ( + QApplication, QWidget, QHBoxLayout, QVBoxLayout, QPushButton, + QComboBox, QScrollArea, QLabel, QFormLayout, QLineEdit, QGroupBox, QSizePolicy +) +from PySide6.QtCore import Qt, QRegularExpression +from PySide6.QtGui import QPixmap, QDoubleValidator, QRegularExpressionValidator + +# Import constants from Common +from common import * + + +class NoScrollComboBox(QComboBox): + def wheelEvent(self, event): + event.ignore() + + +def right_aligned_widget(widget): + container = QWidget() + layout = QHBoxLayout(container) + layout.setContentsMargins(0, 0, 0, 0) + layout.addWidget(widget) + layout.setAlignment(widget, Qt.AlignVCenter) + return container + + +def left_aligned_widget(widget): + container = QWidget() + layout = QHBoxLayout(container) + layout.setContentsMargins(0, 0, 0, 0) + layout.addWidget(widget) + layout.addStretch() + layout.setAlignment(widget, Qt.AlignVCenter) + return container + + +def apply_field_style(widget): + widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) + if isinstance(widget, QComboBox): + style = """ + QComboBox { + padding: 2px; + border: 1px solid black; + border-radius: 5px; + background-color: white; + color: black; + } + QComboBox::drop-down { + subcontrol-origin: padding; + subcontrol-position: top right; + border-left: 0px; + } + QComboBox QAbstractItemView { + background-color: white; + border: 1px solid black; + outline: none; + } + QComboBox QAbstractItemView::item { + color: black; + background-color: white; + padding: 2px; + } + QComboBox QAbstractItemView::item:hover { + background-color: #90AF13; + color: black; + } + """ + widget.setStyleSheet(style) + elif isinstance(widget, QLineEdit): + widget.setStyleSheet(""" + QLineEdit { + padding: 1px 7px; + border: 1px solid #070707; + border-radius: 6px; + background-color: white; + color: #000000; + } + """) + + +def style_main_buttons(): + return """ + QPushButton { + background-color: #94b816; + color: white; + font-weight: bold; + border-radius: 4px; + padding: 6px 18px; + } + QPushButton:hover { + background-color: #7a9a12; + } + """ + + +class InputDock(QWidget): + def __init__(self, backend, parent): + super().__init__() + self.parent = parent + self.backend = backend + self.input_widget = None + + self.setStyleSheet("background: transparent;") + self.main_layout = QHBoxLayout(self) + self.main_layout.setContentsMargins(0, 0, 0, 0) + self.main_layout.setSpacing(0) + + self.left_container = QWidget() + + # Get input fields from backend + input_field_list = self.backend.input_values() + input_field_list = self.equalize_label_length(input_field_list) + + self.build_left_panel(input_field_list) + self.main_layout.addWidget(self.left_container) + + # Toggle strip + self.toggle_strip = QWidget() + self.toggle_strip.setStyleSheet("background-color: #94b816;") + self.toggle_strip.setFixedWidth(6) + toggle_layout = QVBoxLayout(self.toggle_strip) + toggle_layout.setContentsMargins(0, 0, 0, 0) + toggle_layout.setSpacing(0) + toggle_layout.setAlignment(Qt.AlignVCenter | Qt.AlignLeft) + + self.toggle_btn = QPushButton("<") + self.toggle_btn.setFixedSize(6, 60) + self.toggle_btn.setToolTip("Hide panel") + self.toggle_btn.setStyleSheet(""" + QPushButton { + background-color: #6c8408; + color: white; + font-size: 12px; + font-weight: bold; + border: none; + } + QPushButton:hover { + background-color: #5e7407; + } + """) + toggle_layout.addStretch() + toggle_layout.addWidget(self.toggle_btn) + toggle_layout.addStretch() + self.main_layout.addWidget(self.toggle_strip) + + def equalize_label_length(self, list): + max_len = 0 + for t in list: + if t[2] not in [TYPE_TITLE, TYPE_IMAGE]: + if len(t[1]) > max_len: + max_len = len(t[1]) + + return_list = [] + for t in list: + if t[2] not in [TYPE_TITLE, TYPE_IMAGE]: + new_tupple = (t[0], t[1].ljust(max_len)) + t[2:] + else: + new_tupple = t + return_list.append(new_tupple) + + return return_list + + def get_validator(self, validator): + if validator == 'Int Validator': + return QRegularExpressionValidator(QRegularExpression("^(0|[1-9]\\d*)(\\.\\d+)?$")) + elif validator == 'Double Validator': + return QDoubleValidator() + else: + return None + + def build_left_panel(self, field_list): + left_layout = QVBoxLayout(self.left_container) + left_layout.setContentsMargins(0, 0, 0, 0) + left_layout.setSpacing(0) + + self.left_panel = QWidget() + self.left_panel.setStyleSheet("background-color: white;") + panel_layout = QVBoxLayout(self.left_panel) + panel_layout.setContentsMargins(5, 5, 5, 5) + panel_layout.setSpacing(0) + + # Top Bar + top_bar = QHBoxLayout() + top_bar.setSpacing(10) + input_dock_btn = QPushButton("Basic Inputs") + input_dock_btn.setStyleSheet(style_main_buttons()) + input_dock_btn.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) + top_bar.addWidget(input_dock_btn) + + additional_inputs_btn = QPushButton("Additional Inputs") + additional_inputs_btn.setStyleSheet(style_main_buttons()) + additional_inputs_btn.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) + top_bar.addWidget(additional_inputs_btn) + panel_layout.addLayout(top_bar) + + # Scroll area + scroll_area = QScrollArea() + scroll_area.setWidgetResizable(True) + scroll_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + scroll_area.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) + scroll_area.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) + scroll_area.setStyleSheet(""" + QScrollArea { + border: 1px solid #EFEFEC; + background-color: transparent; + padding: 3px; + } + QScrollBar:vertical { + background: #E0E0E0; + width: 8px; + margin: 0px 0px 0px 3px; + border-radius: 2px; + } + QScrollBar::handle:vertical { + background: #A0A0A0; + min-height: 30px; + border-radius: 2px; + } + QScrollBar::handle:vertical:hover { + background: #707070; + } + """) + + group_container = QWidget() + self.input_widget = group_container + group_container_layout = QVBoxLayout(group_container) + + # Build form + track_group = False + index = 0 + for field in field_list: + index += 1 + label = field[1] + type = field[2] + + if type == TYPE_MODULE: + continue + elif type == TYPE_TITLE: + if track_group: + current_group.setLayout(cur_box_form) + group_container_layout.addWidget(current_group) + track_group = False + + current_group = QGroupBox(label) + current_group.setObjectName(label + "_group") + track_group = True + current_group.setStyleSheet(""" + QGroupBox { + border: 1px solid #90AF13; + border-radius: 4px; + margin-top: 0.8em; + font-weight: bold; + } + QGroupBox::title { + subcontrol-origin: content; + subcontrol-position: top left; + left: 10px; + padding: 0 4px; + margin-top: -15px; + background-color: white; + } + """) + cur_box_form = QFormLayout() + cur_box_form.setHorizontalSpacing(5) + cur_box_form.setVerticalSpacing(10) + cur_box_form.setContentsMargins(10, 10, 10, 10) + cur_box_form.setLabelAlignment(Qt.AlignmentFlag.AlignLeft) + cur_box_form.setAlignment(Qt.AlignmentFlag.AlignRight) + + elif type == TYPE_COMBOBOX or type == TYPE_COMBOBOX_CUSTOMIZED: + left = QLabel(label) + left.setObjectName(field[0] + "_label") + + right = NoScrollComboBox() + right.setObjectName(field[0]) + apply_field_style(right) + option_list = field[3] + right.addItems(option_list) + + cur_box_form.addRow(left, right_aligned_widget(right)) + + elif type == TYPE_IMAGE: + left = "" + right = QLabel() + right.setFixedWidth(90) + right.setFixedHeight(90) + right.setObjectName(field[0]) + right.setScaledContents(True) + # Try to load image if path exists + try: + pixmap = QPixmap(field[3]) + if not pixmap.isNull(): + right.setPixmap(pixmap) + except: + right.setText("Image") + right.setAlignment(Qt.AlignmentFlag.AlignLeft) + cur_box_form.addRow(left, left_aligned_widget(right)) + + elif type == TYPE_TEXTBOX: + left = QLabel(label) + left.setObjectName(field[0] + "_label") + + right = QLineEdit() + apply_field_style(right) + right.setObjectName(field[0]) + right.setEnabled(True if field[4] else False) + if field[5] != 'No Validator': + right.setValidator(self.get_validator(field[5])) + cur_box_form.addRow(left, right_aligned_widget(right)) + + if index == len(field_list): + current_group.setLayout(cur_box_form) + group_container_layout.addWidget(current_group) + + group_container_layout.addStretch() + scroll_area.setWidget(group_container) + + self.data = {} + panel_layout.addWidget(scroll_area) + + # Bottom buttons + btn_button_layout = QHBoxLayout() + btn_button_layout.setContentsMargins(0, 20, 0, 0) + btn_button_layout.addStretch(2) + + save_input_btn = QPushButton("Save Input") + save_input_btn.setStyleSheet(style_main_buttons()) + btn_button_layout.addWidget(save_input_btn) + btn_button_layout.addStretch(1) + + design_btn = QPushButton("Design") + design_btn.setStyleSheet(style_main_buttons()) + btn_button_layout.addWidget(design_btn) + btn_button_layout.addStretch(2) + + panel_layout.addLayout(btn_button_layout) + + # Horizontal scroll area + h_scroll_area = QScrollArea() + h_scroll_area.setWidgetResizable(True) + h_scroll_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded) + h_scroll_area.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + h_scroll_area.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) + h_scroll_area.setStyleSheet(""" + QScrollArea { + border: none; + background-color: transparent; + } + QScrollBar:horizontal { + background: #E0E0E0; + height: 8px; + margin: 3px 0px 0px 0px; + border-radius: 2px; + } + QScrollBar::handle:horizontal { + background: #A0A0A0; + min-width: 30px; + border-radius: 2px; + } + """) + h_scroll_area.setWidget(self.left_panel) + + left_layout.addWidget(h_scroll_area) \ No newline at end of file diff --git a/src/osbridge/resources/resources.qrc b/src/osbridge/resources/resources.qrc new file mode 100644 index 00000000..e69de29b diff --git a/src/osbridge/template_page.py b/src/osbridge/template_page.py new file mode 100644 index 00000000..81b340fc --- /dev/null +++ b/src/osbridge/template_page.py @@ -0,0 +1,184 @@ +import sys +from PySide6.QtWidgets import ( + QApplication, QWidget, QVBoxLayout, QHBoxLayout, QLabel, + QMenuBar, QSplitter, QSizePolicy +) +from PySide6.QtCore import Qt + +from input_dock import InputDock +from backend import BackendOsBridge +from common import * + +class DummyCADWidget(QWidget): + """Placeholder for CAD widget""" + def __init__(self): + super().__init__() + layout = QVBoxLayout(self) + label = QLabel("CAD Window\n(Placeholder)") + label.setAlignment(Qt.AlignCenter) + label.setStyleSheet(""" + QLabel { + background-color: #f0f0f0; + border: 2px dashed #999; + padding: 40px; + font-size: 18px; + color: #666; + } + """) + layout.addWidget(label) + + +class DummyOutputDock(QWidget): + """Placeholder for output dock""" + def __init__(self): + super().__init__() + layout = QVBoxLayout(self) + label = QLabel("Output Dock\n(Placeholder)") + label.setAlignment(Qt.AlignCenter) + label.setStyleSheet(""" + QLabel { + background-color: #e8f5e9; + border: 2px dashed #4caf50; + padding: 20px; + font-size: 16px; + color: #2e7d32; + } + """) + layout.addWidget(label) + self.hide() # Hidden initially + + +class DummyLogDock(QWidget): + """Placeholder for log dock""" + def __init__(self): + super().__init__() + layout = QVBoxLayout(self) + label = QLabel("Log Window\n(Placeholder)") + label.setAlignment(Qt.AlignCenter) + label.setStyleSheet(""" + QLabel { + background-color: #fff3e0; + border: 2px dashed #ff9800; + padding: 20px; + font-size: 14px; + color: #e65100; + } + """) + layout.addWidget(label) + self.hide() # Hidden initially + + +class CustomWindow(QWidget): + def __init__(self, title: str, backend: object, parent=None): + super().__init__() + self.parent = parent + self.backend = backend() + + self.setWindowTitle(title) + self.setStyleSheet(""" + QWidget { + background-color: #ffffff; + margin: 0px; + padding: 0px; + } + QMenuBar { + background-color: #F4F4F4; + color: #000000; + padding: 0px; + } + QMenuBar::item { + padding: 5px 10px; + background: transparent; + } + QMenuBar::item:selected { + background: #FFFFFF; + } + """) + + self.init_ui(title) + + def init_ui(self, title: str): + main_v_layout = QVBoxLayout(self) + main_v_layout.setContentsMargins(0, 0, 0, 0) + main_v_layout.setSpacing(0) + + # Menu bar + self.menu_bar = QMenuBar(self) + self.menu_bar.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed) + self.menu_bar.setFixedHeight(28) + self.menu_bar.setContentsMargins(0, 0, 0, 0) + + # Add simple menus + file_menu = self.menu_bar.addMenu("File") + edit_menu = self.menu_bar.addMenu("Edit") + help_menu = self.menu_bar.addMenu("Help") + + main_v_layout.addWidget(self.menu_bar) + + # Body widget + self.body_widget = QWidget() + self.layout = QHBoxLayout(self.body_widget) + self.layout.setContentsMargins(0, 0, 0, 0) + self.layout.setSpacing(0) + + # Splitter for docks + self.splitter = QSplitter(Qt.Horizontal, self.body_widget) + + # Input dock + self.input_dock = InputDock(backend=self.backend, parent=self) + # Constrain the input dock width to avoid it taking too much space at startup + self.input_dock.setMinimumWidth(220) + self.input_dock.setMaximumWidth(420) + self.splitter.addWidget(self.input_dock) + + # Central widget with CAD and log + central_widget = QWidget() + central_V_layout = QVBoxLayout(central_widget) + central_V_layout.setContentsMargins(0, 0, 0, 0) + central_V_layout.setSpacing(0) + + # CAD widget + self.cad_widget = DummyCADWidget() + central_V_layout.addWidget(self.cad_widget, 3) + + # Log dock + self.logs_dock = DummyLogDock() + central_V_layout.addWidget(self.logs_dock, 1) + + self.splitter.addWidget(central_widget) + + # Output dock + self.output_dock = DummyOutputDock() + self.splitter.addWidget(self.output_dock) + + self.layout.addWidget(self.splitter) + + # Set initial sizes and stretch factors so the input dock is narrower by default + # Give the central widget most of the available space + self.splitter.setStretchFactor(0, 0) # input dock: low stretch + self.splitter.setStretchFactor(1, 1) # central widget: high stretch + self.splitter.setStretchFactor(2, 0) # output dock: low stretch + + # Try to set reasonable default sizes (input small, central large, output hidden) + input_dock_width = 300 + total_width = self.width() if self.width() > 0 else 1200 + central_width = max(600, total_width - input_dock_width) + output_dock_width = 0 + self.splitter.setSizes([input_dock_width, central_width, output_dock_width]) + + main_v_layout.addWidget(self.body_widget) + + +def main(): + app = QApplication(sys.argv) + + # Create and show window + window = CustomWindow("Fin Plate Connection - Test", BackendOsBridge, None) + window.resize(1200, 800) + window.show() + + sys.exit(app.exec()) + + +if __name__ == "__main__": + main() \ No newline at end of file From fd3fcbd759938b380f7cbb15a9770a393e8d99eb Mon Sep 17 00:00:00 2001 From: Garvit S Rathore Date: Thu, 16 Oct 2025 00:03:07 +0530 Subject: [PATCH 02/59] Fix initial size of input dock and fix overflowing of items inside input dock. --- .../__pycache__/input_dock.cpython-311.pyc | Bin 18172 -> 18653 bytes src/osbridge/input_dock.py | 15 ++++++++-- src/osbridge/template_page.py | 28 ++++++++++-------- 3 files changed, 27 insertions(+), 16 deletions(-) diff --git a/src/osbridge/__pycache__/input_dock.cpython-311.pyc b/src/osbridge/__pycache__/input_dock.cpython-311.pyc index aee195eb2d6b344d8ff659cbead9c836560582d6..b669be4662daba761d1ffb34791288dbdb98a50e 100644 GIT binary patch delta 1692 zcmah}TWl0n7@o6xX0Ppby6xVebc^k7**m>JSxU?HhUL;yOA8bt!?L^VPT202*&&o> zQTkxS3kLF=AP<DE!qMg<;B)MzY*M@>9uTK2(9Jjs0XU(Wyk z-?{wfj|=F#OUQDqy4u8$G5h0hnfcC}7SJL;yu-c>kzg%s!!8KmI;h7sRZr*!Vm{5> zrh!yWN{OL(R!HZXP7BF&N@~jFMPV+Vlj7Oz8Df(%Las@W3QY-W6LKk0)Q~bh1H6LW zW>Y+w+$V`CDUpF|*sU}7(Nu5`kLk~oS?8D^poNWCA7ayeY+J&%5_Txqv4|ZopzkzU zN;sn6$Rduwyxu8aW@}KlPcyn&K*S`2$Wmyg;Gxh$!AqfrK)Fh-w|`|F@U1S0++fsS zF>XN&bD1eJ3vkI@4|nxFn8+ADcef%l%;=opYPOg}S5k8p5r-b)UK_j@sBt1D$?Rh! zGs(R=ij2fD4AeH;;hcd*dXmr%HV+3M8jN~8`qLL=p8T=lYb`Ru8&&r8V`Az!*`rjl z%142^D!0oDi+oL4PN4~;!0_PZYf1FF$d3CMg3g0m+8~aMQ zU%~y0xL@_HS;A}H>L_8ig58VQEw@)cKx{XOJt*624(U-BoV43fGn}_yKw+>uwp!^% z`E;U-{KO#&gK*3da}5x2&bl)LN zh17#;Z>=YbBuA#Lm3ZJ=+t5+C)apW$G=jAjtqX1Blx*-lnb-bQf}DRd>R8djwb;l= z0-W6tK%0oEo-~C;SFn-D%~0A9A=`<39q_K-1`j+AIO=Pt)GLsBez@ynZPGuOKU!se zn)1+ECCYmeZ6iz_@R`3C`h#{Yv(T&R{y?yjlZSjDgkFTJfitX7G%mzeDb=Aqb-k}P z*BB5Zkn7C%Lq0fU96Qqm$B=okbd*v6y(`Y3^)@(Fubq5JS@qk>{@jA8+h&rn6 zq@Xs_Fx>32`MapDc9@<@^g@a46f6YF#-Z7ncrGdA(qbq4+y!+zsMS0%H8#YL>>Zrk zGbB#I#Vyj*UTUu=_k4$t$|j!`^QWcESUxKx&XnsaIaFVPI?EiLnir?l=A9;Q&}ttK zuk>-*pgJb@KLnGoT9eF6k6mb(US=6f#cn7-7R7% z?2=#Wj%f@N$QVP9nF;yJo<_9&A=hx9Yg^*l?gW(f=StkT!i_I-x7?ANkBgV;#xyD2nLpbaMO zrl7LhL!@#w&nL3+LLrgLW_jL+#7SZ;kSB_XiEHW9o~Muo=io@BU(1G;ng2&|of-#k z4!&=uFUraR_Q3GaI&@1uKIB9%n2>FmslXaNRVz>*MAgeo1&*L9WLRb@&^Uq-;qmWy OQU6%=Joyj5%Iz;sQ_9r< delta 1357 zcmaiz-%nd*7{|}k_MEmr%V|ex8&=q$(4PLF!Wi0iY^|5_Mslva+J#=sc%cZ7H~JSHM zuG;T_2i=Z{wVNg-fjH@dyu$D@UUtMFj_VFPOe^0whIKH6O)fXAC}G#{0H0u>EwB07 zqF`Gm5B>P1`wdX+T=WW1S9Sv|4ce~p0rbq7|rn~O1OYCoOrTVg29yy3{ z++LJCl*e)1lYuk1q;p`0Hw-C!>UZG{uN8c_(Qd1ZtaR za61r$t7r;d=U*xt*G|yNCaUHT45)AUEZ7VuaXWY$PT+L6Ldjmq7rL+dUCK^q9`;HV z(o?S$&QfV4t-ROwzRr4z2+SK~P^UMEN5eMvaptQdt!EY+2-3sA=85z&9K(xYJan3x zmHMg7L?SynemXspAg`kkk!_=_gbjYBPQ5^rh1oe7HzR%RZ0srqdOe2Z{Cue*P-}AW zDhaTLa||OWMPJ2tqP_Jw=4KeW6(za^FpQl8ZNg(3DS4IQftbcH3C0-ga+Au+sa9}3 z7Z3j=N5R+SC@_R8!4P__xM0a5`a&juH*$Ug!R-WG7R 0 else 1200 - central_width = max(600, total_width - input_dock_width) + # Set initial sizes - optimized input dock width + input_dock_width = 320 output_dock_width = 0 + total_width = self.width() if self.width() > 0 else 1200 + central_width = max(600, total_width - input_dock_width - output_dock_width) + self.splitter.setSizes([input_dock_width, central_width, output_dock_width]) - + main_v_layout.addWidget(self.body_widget) From 25d9a111f2321acbb98b1d42d24826eabfbb93fc Mon Sep 17 00:00:00 2001 From: Garvit S Rathore Date: Thu, 16 Oct 2025 00:28:57 +0530 Subject: [PATCH 03/59] FIx: blank space in borderline and Improvised design and alignment --- .../__pycache__/input_dock.cpython-311.pyc | Bin 18653 -> 19387 bytes src/osbridge/input_dock.py | 176 ++++++++++-------- 2 files changed, 98 insertions(+), 78 deletions(-) diff --git a/src/osbridge/__pycache__/input_dock.cpython-311.pyc b/src/osbridge/__pycache__/input_dock.cpython-311.pyc index b669be4662daba761d1ffb34791288dbdb98a50e..004e68d84210b440a5985fbad212ffc2ab720d20 100644 GIT binary patch delta 3931 zcmb7G4QyN06@J%wet!OW{)vC{{Q<|RDEyrM+lNQ~6Q0-pMTR)|NOl-$V5DL@ zU5h9RuIOvwICBJ^;SCU_Ti^+r@5pM1ROva&uC=BV6m?NXQJ3*odJr{@_EH|%RQsO9 z#E~q#O^1*Ybn^ChQoLA_48e!06Kk7=`_xTZqDm&7jE`j#=@bt=79$gn zjJJ=EW5s>&lKPMwuhyx8*Mm064sEbS)4Ik|uQOvqRmp{R5_gw0-v7s45)pugI zHsdez8iv!z}=gm0`nFr4qn!- zmtVjJQ9+HR0Y){g(4jKIWs3u}93$?$if zuiFOC>hy|9F`mi9V=$y?V8TK~2>9Wzx(%U?c*}9JcXGT>Q$?9x!xkwFq8$^)@K=7; zj`&Aj7}htb&QK4c0}MXuG(4uSm;ISu_@Vv^f{O+n{Jq`+mn$QjZcTQYI;w+WWl&wF z%<19!3Daj?Yi#5YVn(6C0#n9OIP7nLue+4+V=o7v8I=|(*6SBeqlLKCmTE$8S3^f{L(=-Hlil4VG-NAy zgy>|$sep!`m?Lt3&(=T?J~bbLt2#ZzEWche=-CdN%u@q{)&SZ7r>p~T)p{p#L!)gg z(}xulx5A{Y8}-7x&B{z+LdFAsusydhTlHHWyhD_S3D+u4tGgTb%chl>(}z3P99Ho94E~sUMioOd>S?rvnxzk= zW;E04m}Uy6I!x2VG*xT1qGmYU=iu$ePPuVfCANT%AC|?`kZ$Ul zARWBhlqPGzq2?-uDyEOAVk($v-iBReRhQj4r2w?fj2hu=vl%{U)}SV0#Vs)* zc1Kwa>OLv7GHOiS`9-Suv+Qn|Uw5a>FauE9cfr+5Wq36KWx?3Pbj8_@kSb*W#590%|fUbTQM60k42(VrD9_h>z3` zWCqk?ME)CX_|y6@3P^PYo1a!v**G{_Y_Q#}rDsgjCV0a`LP^VH%y^P^=<)hcND{Ze zqh31-VtPyod_Wc!aLEZ2kpFq_x&k*C0P~6dMfX;dj)xeXjdn+t+Dk%*9?P`Fx>Z{>t zpY0ruAHq-z{l2{w)>|07Q1sm+1^cLf)oj0IPqw+z@?u~B)xoQQdc?we0gp_05sa2@ z=*FdBvV&HSrITqf>U0HzL3qmIU{Y!En2?0lh)v3*rLv8?^|)>a!X_9DE16O%mAbfm zlT?!a zjV%?|s1y8QGjvCEIOK*R0r0i6@Xer8E-43BB38%+`SMsRQ(ku6x~LF~@kv|;l}a=gJCz!g?Yk$lYa~$3l^_DwW=@alN1ielT&Tu=~R|Kab5Y6 z|2mzY^J}M5;>aYX@I@%#$0cx;1o#TIdVu`o{p58AwWYgj* zsYz3+y}|==XMbw)cy@&WXNQ8G#fG`=&F|gTlafWDC0wOk^CYqD-DSu5;ogYK=;;v| zyok7+07;MH0Mu_VdpD3K(MZJCXi6ltSPT--Vpvi3J~%0)Vu{p*7=n8@fPEXGs)z3y z>Kz^2wPoLq-W8S98SWq6(F@Ax{mc*%cEic24+_zshg@t{SR5ez+4!-{3WHmF(kDc6 z;l3`~KiJdPEA9eM=PBh~gj|t~rxxZrGYAbr)5b>hG7N0|QBxq#+6$~b$J&=!-4ffd z$TrLc=7dXzOPlkoufY0ptnYo$Y)V%ZDJ_q0I9hVU!GOCM>L_I$%x-E$SK+lykD4ql z%wZuM7>Wcw@xy^GN8d7Ax5Ne(*}#?hE4yDoui#}j7TAqBcB3@nS!6wz8uBb(VEG)& z7a7W0dq%yiH=L0#n;k`JA5wOsYt%kC*LA^U;7!*kgTeeUfg*uV8ex0)LG$l77j_&h z>`3H&iM;-3L4P!-Ke`O_-Ij{Heh+-uy~(_+H!bNK7WEA|*H`lT{RRF0oPIy>J?|hH zs5YNPd3btr=*zhl@|$-d)t)@-F0k$#>jv)@FN8MLR#q*su0_^0w=>TM3v4jQ24QN8 zx3zMKbuO~b3pIuM@T=-Vq(9FN6xe|rJFv{Ii42rY48Pg(m`U4ot9@ajH+1Sv^QIA# z>@AaZgqM31VjYPE2LUI6UIN6$;vj|oaB&p`wof z3E|Sg=mJllU3D4viI$m1V5slSimQ|fyxT$#vk3dR3>F3Hmceuf!eLg1hmir{J8l`= WyJ&oEt08K-a5JI5Ykx{dVPMP43R(s%zBK40=+jnkWWfV<2 z*Z1dhzVDp#J4V z0fxR_*~rb0u-6!o!u!fvX=CCHlxi$+POo9dr^hBvV9N@tI!+ow&N*!mMs%f+R%>8U zbprev1$3(AkXDt#ZmkKP)AX;8`chK~ZQ3VDBV1583wWGp6pqgt$%?lE>hAS`# zdLD$ne7xY(`g(+ z%@*rX>qcFufke3luGy@l9{z4C(KT zSw_@VCJrjI+29R3E3z8ajGznyP{v6|=eo<^c09~APqZ`zTG~~xx7CjRe^(xgg_|&@ z3Qi{}231i53B!FwLpE#d6cq)~};!zGNcSz{PsV}+4+O!7m*`vqLije_H(V5oHzr6eb0 zl*n7}#XdYQIX^JT%*#Z1NB=K*^(cdE=|&}PwK7t==uIif%V^ST_^y>>5^7MCdGao! z=8!ed#*s76reqnJ7a7rAMa{>AQ+zxkGF0){>4-i3937s8xpYf0}J4bNkz}NNT{qj(CodVF6)v47_| zHphZmoQ+j(FE9xAo^9Ff$&eSKneNQkOFLc}dU5Dir*jqId__20(sH%o>b|S{ukL?8 zlq=~dhh0+>%sWVew2lj4G$|QjYHM1m5#9M0eJly#;kZqbw8GOq2lV({=xRFX)$K;zAto+8 zBR#~NCOB^kk}dFNRq2*vH+4^Qa=NU3SOAGPeD)8I*->0GN|PiiQ9QfvOUt9`PRS~v>< zWf64MMcAp7GSekq6N*f?Xz6O5o%Fz+x+$kMN}}XtJRx5bPf02ZJ#EGQ)YEE6`%8!o zUiUu$2kO1D4F)_|WrU1hOYCsIp0XUUD`1jt4I2*3ihJYJzzwV8J0?2T)%bJ6F5(1V zV;Ny#xX~*e9h-rMre?T5tXA~(qc08cXquQi*Yt63}W4$ub@ zK{tF9s{BqzKf9J=edTmI`gxjq@DC!a$Rm^d?5V`qFzz7Ohr;)?D4{s)m$wGG`x}C= zH@u$>_*-H#kPUmF(8ys@V}he$4vvI$;{TqOPHtir6XCef4r479@Gq4PKE(gqZ8ivm z6>v1D0z<0@&5|qYgxN$}XFUmDMrKP7;x!%}6K46F79ed7@&y#Ny{iyC8EA(q&CYdSa@yd!D@B)nuR9yI zTxf%*+MS?5y*GSbWcK#*^fuuKDU?BPZ*`ogqI?`vK`fhKl*BLyA8in8xaVk{n= zicj)Q@b7l8^;4=^y!3YO*>PaFk3WPIc;mj4WDitz)O!udn9x7V&xoG|Gw9UyFNB?I zUkEFl$QYeXz-UL)wpKbFHx`W!3bC0a-jvbk{@K$!{rcdk*Z2{5tK+OPMyp2<7F#=$ zgbc%#`zy#0EZzTGbz_cm=Q($lb65@i>;#f-wSo?lw@b(UL< z-e|o|p+MoAQt0Ha#7Q zcnN<1sY#k0b*-f=9dry5w?xkE@8OGj{|9S$6WRa( diff --git a/src/osbridge/input_dock.py b/src/osbridge/input_dock.py index 0d81c107..69cbb653 100644 --- a/src/osbridge/input_dock.py +++ b/src/osbridge/input_dock.py @@ -6,7 +6,6 @@ from PySide6.QtCore import Qt, QRegularExpression from PySide6.QtGui import QPixmap, QDoubleValidator, QRegularExpressionValidator -# Import constants from Common from common import * @@ -16,15 +15,13 @@ def wheelEvent(self, event): def right_aligned_widget(widget): - """Center-align widget horizontally within its container""" + """Right-align widget horizontally within its container""" container = QWidget() layout = QHBoxLayout(container) layout.setContentsMargins(0, 0, 0, 0) - # Add stretches on both sides to center the widget layout.addStretch() layout.addWidget(widget) - layout.addStretch() - layout.setAlignment(widget, Qt.AlignCenter) + layout.setAlignment(widget, Qt.AlignRight | Qt.AlignVCenter) return container @@ -39,36 +36,48 @@ def left_aligned_widget(widget): def apply_field_style(widget): - # Use Preferred size policy to prevent overflow beyond dock boundaries widget.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed) - # Set maximum width to ensure controls don't overflow the dock - widget.setMaximumWidth(200) + widget.setMaximumWidth(180) + widget.setMinimumHeight(24) + if isinstance(widget, QComboBox): style = """ QComboBox { - padding: 2px; - border: 1px solid black; - border-radius: 5px; + padding: 3px 5px; + border: 1px solid #c0c0c0; + border-radius: 3px; background-color: white; color: black; + min-height: 20px; } QComboBox::drop-down { subcontrol-origin: padding; subcontrol-position: top right; - border-left: 0px; + width: 20px; + border-left: 1px solid #c0c0c0; + background-color: #4a7ba7; + } + QComboBox::down-arrow { + image: none; + border-left: 4px solid transparent; + border-right: 4px solid transparent; + border-top: 6px solid white; + margin-right: 5px; } QComboBox QAbstractItemView { background-color: white; - border: 1px solid black; + border: 1px solid #c0c0c0; outline: none; + selection-background-color: #e8f4ff; } QComboBox QAbstractItemView::item { color: black; background-color: white; - padding: 2px; + padding: 4px; + min-height: 20px; } QComboBox QAbstractItemView::item:hover { - background-color: #90AF13; + background-color: #e8f4ff; color: black; } """ @@ -76,11 +85,15 @@ def apply_field_style(widget): elif isinstance(widget, QLineEdit): widget.setStyleSheet(""" QLineEdit { - padding: 1px 7px; - border: 1px solid #070707; - border-radius: 6px; + padding: 3px 5px; + border: 1px solid #c0c0c0; + border-radius: 3px; background-color: white; - color: #000000; + color: black; + min-height: 20px; + } + QLineEdit:focus { + border: 1px solid #4a7ba7; } """) @@ -88,15 +101,20 @@ def apply_field_style(widget): def style_main_buttons(): return """ QPushButton { - background-color: #94b816; + background-color: #90AF13; color: white; font-weight: bold; - border-radius: 4px; - padding: 6px 18px; + border: 1px solid #7a9611; + border-radius: 3px; + padding: 6px 16px; + min-height: 28px; } QPushButton:hover { background-color: #7a9a12; } + QPushButton:pressed { + background-color: #6a8a10; + } """ @@ -116,14 +134,13 @@ def __init__(self, backend, parent): # Get input fields from backend input_field_list = self.backend.input_values() - input_field_list = self.equalize_label_length(input_field_list) self.build_left_panel(input_field_list) self.main_layout.addWidget(self.left_container) # Toggle strip self.toggle_strip = QWidget() - self.toggle_strip.setStyleSheet("background-color: #94b816;") + self.toggle_strip.setStyleSheet("background-color: #90AF13;") self.toggle_strip.setFixedWidth(6) toggle_layout = QVBoxLayout(self.toggle_strip) toggle_layout.setContentsMargins(0, 0, 0, 0) @@ -135,14 +152,14 @@ def __init__(self, backend, parent): self.toggle_btn.setToolTip("Hide panel") self.toggle_btn.setStyleSheet(""" QPushButton { - background-color: #6c8408; + background-color: #7a9a12; color: white; font-size: 12px; font-weight: bold; border: none; } QPushButton:hover { - background-color: #5e7407; + background-color: #6a8a10; } """) toggle_layout.addStretch() @@ -150,23 +167,6 @@ def __init__(self, backend, parent): toggle_layout.addStretch() self.main_layout.addWidget(self.toggle_strip) - def equalize_label_length(self, list): - max_len = 0 - for t in list: - if t[2] not in [TYPE_TITLE, TYPE_IMAGE]: - if len(t[1]) > max_len: - max_len = len(t[1]) - - return_list = [] - for t in list: - if t[2] not in [TYPE_TITLE, TYPE_IMAGE]: - new_tupple = (t[0], t[1].ljust(max_len)) + t[2:] - else: - new_tupple = t - return_list.append(new_tupple) - - return return_list - def get_validator(self, validator): if validator == 'Int Validator': return QRegularExpressionValidator(QRegularExpression("^(0|[1-9]\\d*)(\\.\\d+)?$")) @@ -183,21 +183,38 @@ def build_left_panel(self, field_list): self.left_panel = QWidget() self.left_panel.setStyleSheet("background-color: white;") panel_layout = QVBoxLayout(self.left_panel) - panel_layout.setContentsMargins(5, 5, 5, 5) + panel_layout.setContentsMargins(8, 8, 8, 8) panel_layout.setSpacing(0) - # Top Bar + # Top Bar with buttons top_bar = QHBoxLayout() - top_bar.setSpacing(10) + top_bar.setSpacing(8) + top_bar.setContentsMargins(0, 0, 0, 10) + input_dock_btn = QPushButton("Basic Inputs") input_dock_btn.setStyleSheet(style_main_buttons()) input_dock_btn.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) top_bar.addWidget(input_dock_btn) additional_inputs_btn = QPushButton("Additional Inputs") - additional_inputs_btn.setStyleSheet(style_main_buttons()) + additional_inputs_btn.setStyleSheet(""" + QPushButton { + background-color: white; + color: #333; + font-weight: normal; + border: 1px solid #c0c0c0; + border-radius: 3px; + padding: 6px 16px; + min-height: 28px; + } + QPushButton:hover { + background-color: #f5f5f5; + border: 1px solid #a0a0a0; + } + """) additional_inputs_btn.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) top_bar.addWidget(additional_inputs_btn) + panel_layout.addLayout(top_bar) # Scroll area @@ -208,29 +225,33 @@ def build_left_panel(self, field_list): scroll_area.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) scroll_area.setStyleSheet(""" QScrollArea { - border: 1px solid #EFEFEC; + border: none; background-color: transparent; - padding: 3px; } QScrollBar:vertical { - background: #E0E0E0; - width: 8px; - margin: 0px 0px 0px 3px; - border-radius: 2px; + background: #f0f0f0; + width: 10px; + margin: 0px; + border-radius: 0px; } QScrollBar::handle:vertical { - background: #A0A0A0; + background: #c0c0c0; min-height: 30px; - border-radius: 2px; + border-radius: 0px; } QScrollBar::handle:vertical:hover { - background: #707070; + background: #a0a0a0; + } + QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical { + height: 0px; } """) group_container = QWidget() self.input_widget = group_container group_container_layout = QVBoxLayout(group_container) + group_container_layout.setContentsMargins(0, 0, 0, 0) + group_container_layout.setSpacing(12) # Build form track_group = False @@ -253,32 +274,33 @@ def build_left_panel(self, field_list): track_group = True current_group.setStyleSheet(""" QGroupBox { - border: 1px solid #90AF13; + border: 1px solid #d0d0d0; border-radius: 4px; - margin-top: 0.8em; + margin-top: 8px; font-weight: bold; + font-size: 11px; + padding-top: 8px; } QGroupBox::title { - subcontrol-origin: content; + subcontrol-origin: margin; subcontrol-position: top left; - left: 10px; + left: 8px; padding: 0 4px; - margin-top: -15px; background-color: white; + color: #333; } """) cur_box_form = QFormLayout() - cur_box_form.setHorizontalSpacing(5) - cur_box_form.setVerticalSpacing(10) - cur_box_form.setContentsMargins(10, 10, 10, 10) - cur_box_form.setLabelAlignment(Qt.AlignmentFlag.AlignLeft) - # Center-align the field column for better aesthetics + cur_box_form.setHorizontalSpacing(8) + cur_box_form.setVerticalSpacing(12) + cur_box_form.setContentsMargins(12, 16, 12, 12) + cur_box_form.setLabelAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter) cur_box_form.setFieldGrowthPolicy(QFormLayout.ExpandingFieldsGrow) - cur_box_form.setFormAlignment(Qt.AlignmentFlag.AlignHCenter | Qt.AlignmentFlag.AlignTop) elif type == TYPE_COMBOBOX or type == TYPE_COMBOBOX_CUSTOMIZED: left = QLabel(label) left.setObjectName(field[0] + "_label") + left.setStyleSheet("font-weight: normal; color: #333; font-size: 10px;") right = NoScrollComboBox() right.setObjectName(field[0]) @@ -295,7 +317,6 @@ def build_left_panel(self, field_list): right.setFixedHeight(90) right.setObjectName(field[0]) right.setScaledContents(True) - # Try to load image if path exists try: pixmap = QPixmap(field[3]) if not pixmap.isNull(): @@ -308,6 +329,7 @@ def build_left_panel(self, field_list): elif type == TYPE_TEXTBOX: left = QLabel(label) left.setObjectName(field[0] + "_label") + left.setStyleSheet("font-weight: normal; color: #333; font-size: 10px;") right = QLineEdit() apply_field_style(right) @@ -329,18 +351,18 @@ def build_left_panel(self, field_list): # Bottom buttons btn_button_layout = QHBoxLayout() - btn_button_layout.setContentsMargins(0, 20, 0, 0) - btn_button_layout.addStretch(2) + btn_button_layout.setContentsMargins(0, 15, 0, 0) + btn_button_layout.setSpacing(10) save_input_btn = QPushButton("Save Input") save_input_btn.setStyleSheet(style_main_buttons()) + save_input_btn.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) btn_button_layout.addWidget(save_input_btn) - btn_button_layout.addStretch(1) design_btn = QPushButton("Design") design_btn.setStyleSheet(style_main_buttons()) + design_btn.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) btn_button_layout.addWidget(design_btn) - btn_button_layout.addStretch(2) panel_layout.addLayout(btn_button_layout) @@ -356,15 +378,13 @@ def build_left_panel(self, field_list): background-color: transparent; } QScrollBar:horizontal { - background: #E0E0E0; - height: 8px; - margin: 3px 0px 0px 0px; - border-radius: 2px; + background: #f0f0f0; + height: 10px; + margin: 0px; } QScrollBar::handle:horizontal { - background: #A0A0A0; + background: #c0c0c0; min-width: 30px; - border-radius: 2px; } """) h_scroll_area.setWidget(self.left_panel) From fc770abb07d4fed27ebb709d5dab11131ea52b2d Mon Sep 17 00:00:00 2001 From: Garvit S Rathore Date: Thu, 16 Oct 2025 00:29:41 +0530 Subject: [PATCH 04/59] Changed module name to "Highway Bridge Design" Updated input_values() to return the new fields matching your image: Type of Structure section with dropdown Project Location section with dropdown Geometric Details section with text inputs and dropdown Material Inputs section with three material dropdowns --- .../__pycache__/backend.cpython-311.pyc | Bin 5297 -> 4927 bytes src/osbridge/backend.py | 46 ++++++++---------- 2 files changed, 21 insertions(+), 25 deletions(-) diff --git a/src/osbridge/__pycache__/backend.cpython-311.pyc b/src/osbridge/__pycache__/backend.cpython-311.pyc index aa6668d6bd0391b773225616a51a45283e3346fc..d003537cf6ac6dd2199e72f2099c989428c878bd 100644 GIT binary patch literal 4927 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 A-~a#s diff --git a/src/osbridge/backend.py b/src/osbridge/backend.py index cc0ea892..731d7856 100644 --- a/src/osbridge/backend.py +++ b/src/osbridge/backend.py @@ -1,8 +1,7 @@ -from importlib.resources import files from common import * class BackendOsBridge: - """Simplified backend for Fin Plate Connection""" + """Backend for Highway Bridge Design""" def __init__(self): self.module = KEY_DISP_FINPLATE @@ -16,63 +15,60 @@ def input_values(self): """Return list of input fields for the UI""" options_list = [] - t16 = (KEY_MODULE, KEY_DISP_FINPLATE, TYPE_MODULE, None, True, 'No Validator') - options_list.append(t16) - - t1 = (None, DISP_TITLE_CM, TYPE_TITLE, None, True, 'No Validator') + t1 = (KEY_MODULE, KEY_DISP_FINPLATE, TYPE_MODULE, None, True, 'No Validator') options_list.append(t1) - t2 = (KEY_CONN, KEY_DISP_CONN, TYPE_COMBOBOX, VALUES_CONN, True, 'No Validator') + # Type of Structure section + t2 = (None, DISP_TITLE_STRUCTURE, TYPE_TITLE, None, True, 'No Validator') options_list.append(t2) - # Mock image path - you'll need to provide an actual image - t15 = (KEY_IMAGE, None, TYPE_IMAGE, "path/to/image.png", True, 'No Validator') - options_list.append(t15) - - t3 = (KEY_SUPTNGSEC, KEY_DISP_COLSEC, TYPE_COMBOBOX, connectdb("Columns"), True, 'No Validator') + t3 = (KEY_STRUCTURE_TYPE, KEY_DISP_STRUCTURE_TYPE, TYPE_COMBOBOX, VALUES_STRUCTURE_TYPE, True, 'No Validator') options_list.append(t3) - t4 = (KEY_SUPTDSEC, KEY_DISP_BEAMSEC, TYPE_COMBOBOX, connectdb("Beams"), True, 'No Validator') + # Project Location section + t4 = (None, DISP_TITLE_PROJECT, TYPE_TITLE, None, True, 'No Validator') options_list.append(t4) - t5 = (KEY_MATERIAL, KEY_DISP_MATERIAL, TYPE_COMBOBOX, VALUES_MATERIAL, True, 'No Validator') + t5 = (KEY_PROJECT_LOCATION, KEY_DISP_PROJECT_LOCATION, TYPE_COMBOBOX, VALUES_PROJECT_LOCATION, True, 'No Validator') options_list.append(t5) - t6 = (None, DISP_TITLE_FSL, TYPE_TITLE, None, True, 'No Validator') + # Geometric Details section + t6 = (None, DISP_TITLE_GEOMETRIC, TYPE_TITLE, None, True, 'No Validator') options_list.append(t6) - t7 = (KEY_SHEAR, KEY_DISP_SHEAR, TYPE_TEXTBOX, None, True, 'Int Validator') + t7 = (KEY_SPAN, KEY_DISP_SPAN, TYPE_TEXTBOX, None, True, 'Double Validator') options_list.append(t7) - t8 = (KEY_AXIAL, KEY_DISP_AXIAL, TYPE_TEXTBOX, None, True, 'Int Validator') + t8 = (KEY_CARRIAGEWAY_WIDTH, KEY_DISP_CARRIAGEWAY_WIDTH, TYPE_TEXTBOX, None, True, 'Double Validator') options_list.append(t8) - t9 = (None, DISP_TITLE_BOLT, TYPE_TITLE, None, True, 'No Validator') + t9 = (KEY_FOOTPATH, KEY_DISP_FOOTPATH, TYPE_COMBOBOX, VALUES_FOOTPATH, True, 'No Validator') options_list.append(t9) - t10 = (KEY_D, KEY_DISP_D, TYPE_COMBOBOX_CUSTOMIZED, VALUES_D, True, 'No Validator') + t10 = (KEY_SKEW_ANGLE, KEY_DISP_SKEW_ANGLE, TYPE_TEXTBOX, None, True, 'Double Validator') options_list.append(t10) - t11 = (KEY_TYP, KEY_DISP_TYP, TYPE_COMBOBOX, VALUES_TYP, True, 'No Validator') + # Material Inputs section + t11 = (None, DISP_TITLE_MATERIAL, TYPE_TITLE, None, True, 'No Validator') options_list.append(t11) - t12 = (KEY_GRD, KEY_DISP_GRD, TYPE_COMBOBOX_CUSTOMIZED, VALUES_GRD, True, 'No Validator') + t12 = (KEY_GIRDER, KEY_DISP_GIRDER, TYPE_COMBOBOX, connectdb("Material"), True, 'No Validator') options_list.append(t12) - t13 = (None, DISP_TITLE_PLATE, TYPE_TITLE, None, True, 'No Validator') + t13 = (KEY_CROSS_BRACING, KEY_DISP_CROSS_BRACING, TYPE_COMBOBOX, connectdb("Material"), True, 'No Validator') options_list.append(t13) - t14 = (KEY_PLATETHK, KEY_DISP_PLATETHK, TYPE_COMBOBOX_CUSTOMIZED, VALUES_PLATETHK, True, 'No Validator') + t14 = (KEY_DECK, KEY_DISP_DECK, TYPE_COMBOBOX, connectdb("Material"), True, 'No Validator') options_list.append(t14) return options_list def customized_input(self): - """Return empty list for now - customization not needed for basic test""" + """Return empty list for now""" return [] def input_value_changed(self): - """Return None - no dynamic changes needed for basic test""" + """Return None - no dynamic changes needed""" return None def set_osdaglogger(self, key): From 3582cbae75a7f6a11104e77e4b7d2fbd93f8fabc Mon Sep 17 00:00:00 2001 From: Garvit S Rathore Date: Thu, 16 Oct 2025 00:30:01 +0530 Subject: [PATCH 05/59] Removed old fin plate connection constants Added new highway bridge design constants: Type of Structure Project Location Geometric Details (Span, Carriageway Width, Footpath, Skew Angle) Material Inputs (Girder, Cross Bracing, Deck) --- .../__pycache__/common.cpython-311.pyc | Bin 2185 -> 1967 bytes src/osbridge/common.py | 64 ++++++++---------- 2 files changed, 27 insertions(+), 37 deletions(-) diff --git a/src/osbridge/__pycache__/common.cpython-311.pyc b/src/osbridge/__pycache__/common.cpython-311.pyc index 3d8525e5a208e1595d1a2fb6dab384b020c53612..2db183c3a2b58abc65b0082fe5af29c8d82d3f15 100644 GIT binary patch literal 1967 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 diff --git a/src/osbridge/common.py b/src/osbridge/common.py index c189bdb8..ea63d009 100644 --- a/src/osbridge/common.py +++ b/src/osbridge/common.py @@ -8,50 +8,40 @@ # Keys for inputs KEY_MODULE = "Module" -KEY_CONN = "Connectivity" -KEY_IMAGE = "Image" -KEY_SUPTNGSEC = "Column Section" -KEY_SUPTDSEC = "Beam Section" -KEY_MATERIAL = "Material" -KEY_SHEAR = "Shear Force" -KEY_AXIAL = "Axial Force" -KEY_D = "Bolt Diameter" -KEY_TYP = "Bolt Type" -KEY_GRD = "Bolt Grade" -KEY_PLATETHK = "Plate Thickness" +KEY_STRUCTURE_TYPE = "Structure Type" +KEY_PROJECT_LOCATION = "Project Location" +KEY_SPAN = "Span" +KEY_CARRIAGEWAY_WIDTH = "Carriageway Width" +KEY_FOOTPATH = "Footpath" +KEY_SKEW_ANGLE = "Skew Angle" +KEY_GIRDER = "Girder" +KEY_CROSS_BRACING = "Cross Bracing" +KEY_DECK = "Deck" # Display names -KEY_DISP_FINPLATE = "Fin Plate Connection" -DISP_TITLE_CM = "Connection Properties" -KEY_DISP_CONN = "Connectivity" -KEY_DISP_COLSEC = "Column Section" -KEY_DISP_BEAMSEC = "Beam Section" -KEY_DISP_MATERIAL = "Material" -DISP_TITLE_FSL = "Load Details" -KEY_DISP_SHEAR = "Shear Force (kN)" -KEY_DISP_AXIAL = "Axial Force (kN)" -DISP_TITLE_BOLT = "Bolt Details" -KEY_DISP_D = "Diameter (mm)" -KEY_DISP_TYP = "Type" -KEY_DISP_GRD = "Grade" -DISP_TITLE_PLATE = "Plate Details" -KEY_DISP_PLATETHK = "Thickness (mm)" +KEY_DISP_FINPLATE = "Highway Bridge Design" +DISP_TITLE_STRUCTURE = "Type of Structure" +KEY_DISP_STRUCTURE_TYPE = "Structure Type" +DISP_TITLE_PROJECT = "Project Location" +KEY_DISP_PROJECT_LOCATION = "Project Location" +DISP_TITLE_GEOMETRIC = "Geometric Details" +KEY_DISP_SPAN = "Span (m):" +KEY_DISP_CARRIAGEWAY_WIDTH = "Carriageway Width (m):" +KEY_DISP_FOOTPATH = "Footpath" +KEY_DISP_SKEW_ANGLE = "Skew Angle (degrees):" +DISP_TITLE_MATERIAL = "Material Inputs" +KEY_DISP_GIRDER = "Girder" +KEY_DISP_CROSS_BRACING = "Cross Bracing" +KEY_DISP_DECK = "Deck" # Sample values -VALUES_CONN = ["Column flange-Beam web", "Column web-Beam web"] +VALUES_STRUCTURE_TYPE = ["Simply Supported Bridge", "Continuous Bridge", "Cable-Stayed Bridge", "Arch Bridge"] +VALUES_PROJECT_LOCATION = ["Location 1", "Location 2", "Location 3"] +VALUES_FOOTPATH = ["None", "One Side", "Both Sides"] VALUES_MATERIAL = ["E250 (Fe 410W)A", "E300 (Fe 440)", "E350 (Fe 490)", "E410 (Fe 540)", "E450 (Fe 570)", "E550 (Fe 650)"] -VALUES_D = ["All", "Customized"] -VALUES_TYP = ["Bearing Bolt", "Friction Grip Bolt"] -VALUES_GRD = ["All", "Customized"] -VALUES_PLATETHK = ["All", "Customized"] - def connectdb(table_name, popup=None): """Mock database connection - returns sample data""" - if table_name == "Columns": - return ["UC 305x305x240", "UC 305x305x198", "UC 305x305x158", "UC 254x254x167"] - elif table_name == "Beams": - return ["UB 914x419x388", "UB 914x305x289", "UB 838x292x226", "UB 762x267x197"] - elif table_name == "Material": + if table_name == "Material": return VALUES_MATERIAL return [] \ No newline at end of file From 0807baf2bb3b3e7bd0a7661c60d22eb65db97fe2 Mon Sep 17 00:00:00 2001 From: Garvit S Rathore Date: Thu, 16 Oct 2025 00:41:29 +0530 Subject: [PATCH 06/59] Add structure type --- .../__pycache__/common.cpython-311.pyc | Bin 1967 -> 1908 bytes src/osbridge/common.py | 4 +++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/osbridge/__pycache__/common.cpython-311.pyc b/src/osbridge/__pycache__/common.cpython-311.pyc index 2db183c3a2b58abc65b0082fe5af29c8d82d3f15..3c4406d541d28c8b1d5c6803b96d653e2ba888cc 100644 GIT binary patch delta 178 zcmZ3_|AmitIWI340}y1sf1j~?BJXs)6!8>^bfze|bcPhkMT}AMKw1h&D*$O}Ag!1p zmm=H37^RdV6U?9~zwwd{qj(jcM`n6Pd19r4Q&DD0dg?7!|B{TwRi$MI93`Jrfwg`~;#bJ}1pHiBWYFA_lF-U2xFux#S MJUN}+g$1Mm0B|HPJpcdz delta 237 zcmeyux1OJOIWI340}xDl^FE_>BJXs)bcPi1MT}8$Kw1Jw%L8diAgutTrGT_zifoEZ z3uBa0igYl8rrgF$HjDw9ELGycnYjfyl?uV71qJy8VwM&iQ#InR%u8 zrNuBIVduo8oK)T5lEg}cI&R0J-d=f%q#KibO$d vVIc8~!zMRBr8FniuE+w&Wd!2lf0GN?HgmHvNaizMkjMj~$vW&VEFdia1ARk# diff --git a/src/osbridge/common.py b/src/osbridge/common.py index ea63d009..aab08498 100644 --- a/src/osbridge/common.py +++ b/src/osbridge/common.py @@ -35,7 +35,9 @@ KEY_DISP_DECK = "Deck" # Sample values -VALUES_STRUCTURE_TYPE = ["Simply Supported Bridge", "Continuous Bridge", "Cable-Stayed Bridge", "Arch Bridge"] +# Type of Structure: Defines the application of the steel girder bridge +# Currently only covers highway bridge +VALUES_STRUCTURE_TYPE = ["Highway Bridge", "Other"] VALUES_PROJECT_LOCATION = ["Location 1", "Location 2", "Location 3"] VALUES_FOOTPATH = ["None", "One Side", "Both Sides"] VALUES_MATERIAL = ["E250 (Fe 410W)A", "E300 (Fe 440)", "E350 (Fe 490)", "E410 (Fe 540)", "E450 (Fe 570)", "E550 (Fe 650)"] From 6055aa82c6107d28d6f6ca7ff145ad4467e4e487 Mon Sep 17 00:00:00 2001 From: Garvit S Rathore Date: Thu, 16 Oct 2025 00:43:09 +0530 Subject: [PATCH 07/59] Add handler for other structure selection --- .../__pycache__/input_dock.cpython-311.pyc | Bin 19387 -> 20213 bytes src/osbridge/input_dock.py | 20 +++++++++++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/osbridge/__pycache__/input_dock.cpython-311.pyc b/src/osbridge/__pycache__/input_dock.cpython-311.pyc index 004e68d84210b440a5985fbad212ffc2ab720d20..df76744065005ea16da2fc1efcb4031f38434fc5 100644 GIT binary patch delta 2774 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 SBL1Co`kPGi>woCk!u|nx|G~rn diff --git a/src/osbridge/input_dock.py b/src/osbridge/input_dock.py index 69cbb653..186d605d 100644 --- a/src/osbridge/input_dock.py +++ b/src/osbridge/input_dock.py @@ -1,7 +1,7 @@ import sys from PySide6.QtWidgets import ( QApplication, QWidget, QHBoxLayout, QVBoxLayout, QPushButton, - QComboBox, QScrollArea, QLabel, QFormLayout, QLineEdit, QGroupBox, QSizePolicy + QComboBox, QScrollArea, QLabel, QFormLayout, QLineEdit, QGroupBox, QSizePolicy, QMessageBox ) from PySide6.QtCore import Qt, QRegularExpression from PySide6.QtGui import QPixmap, QDoubleValidator, QRegularExpressionValidator @@ -124,6 +124,7 @@ def __init__(self, backend, parent): self.parent = parent self.backend = backend self.input_widget = None + self.structure_type_combo = None # Store reference to structure type combo box self.setStyleSheet("background: transparent;") self.main_layout = QHBoxLayout(self) @@ -174,6 +175,18 @@ def get_validator(self, validator): return QDoubleValidator() else: return None + + def on_structure_type_changed(self, text): + """Handle structure type combo box changes""" + if text == "Other": + # Show warning message when "Other" is selected + QMessageBox.warning( + self, + "Structure Type Not Supported", + "The selected structure type is not included currently.\n\n" + "This application currently only covers Highway Bridge design.", + QMessageBox.Ok + ) def build_left_panel(self, field_list): left_layout = QVBoxLayout(self.left_container) @@ -308,6 +321,11 @@ def build_left_panel(self, field_list): option_list = field[3] right.addItems(option_list) + # Connect signal for Structure Type combo box + if field[0] == KEY_STRUCTURE_TYPE: + self.structure_type_combo = right + right.currentTextChanged.connect(self.on_structure_type_changed) + cur_box_form.addRow(left, right_aligned_widget(right)) elif type == TYPE_IMAGE: From 6ca3ea67c8738081a9f6c51d12389624386346a3 Mon Sep 17 00:00:00 2001 From: Garvit S Rathore Date: Thu, 16 Oct 2025 00:50:04 +0530 Subject: [PATCH 08/59] Add footpath values --- src/osbridge/common.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/osbridge/common.py b/src/osbridge/common.py index aab08498..d11d6ab6 100644 --- a/src/osbridge/common.py +++ b/src/osbridge/common.py @@ -39,7 +39,7 @@ # Currently only covers highway bridge VALUES_STRUCTURE_TYPE = ["Highway Bridge", "Other"] VALUES_PROJECT_LOCATION = ["Location 1", "Location 2", "Location 3"] -VALUES_FOOTPATH = ["None", "One Side", "Both Sides"] +VALUES_FOOTPATH = ["None", "Left Edge", "Right Edge", "Both Edges"] VALUES_MATERIAL = ["E250 (Fe 410W)A", "E300 (Fe 440)", "E350 (Fe 490)", "E410 (Fe 540)", "E450 (Fe 570)", "E550 (Fe 650)"] def connectdb(table_name, popup=None): From a99693d58b085c54c2fbb9617c41a36f9ebcfa4c Mon Sep 17 00:00:00 2001 From: Garvit S Rathore Date: Thu, 16 Oct 2025 01:11:11 +0530 Subject: [PATCH 09/59] Add validators for inputs --- .../__pycache__/backend.cpython-311.pyc | Bin 4927 -> 6971 bytes src/osbridge/backend.py | 33 +++++++++++++++++- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/src/osbridge/__pycache__/backend.cpython-311.pyc b/src/osbridge/__pycache__/backend.cpython-311.pyc index d003537cf6ac6dd2199e72f2099c989428c878bd..add29206e3bf9f3ecd6ec65624a5fc552f1fc198 100644 GIT binary patch delta 2213 zcmbtUOH3O_7@k=_c-dxQO)v!L0OqNLfC3R|NYUbu04X%WG~fe4Ywuzl%-Za(Lt~k` zx8_n6VO1bSNT^jGQj6NE>M5$KrcG~^*2qZ65>k8UxkLpC_0*X)wwDkhRUM5#|MSoP z_`aFZ{@DEFitTf&wHV-0bMV*PWcjzYuWV5M?uqR~7%#$J1OELjVO#@#uuLf=@_5AH z10cO$1QZgJ^XVh1vTpDh1mH{j%Zz11%%B*vD*)y-Q!c??bKYy2*FvPW7`16kK!e@O zLw(j`z*G8oyvB7g8aE0~_JcsY&LCmO$JH_t5i7R}ixbi%u zGwxRxai%y^%%s@!m2&rMQYz2s+^6ohNS}qp7Av}Nye^5EFk1fK{WkN~R~7!2r)G0*c6jgaTqXqR5Ez`9cyO5dBCAq2X~C>Opc?@C9ZA zK28aQL^)dF4s#+}h{y`^3V13@f*_)30a`$u$fGEO7CMkCB1wX%pa)zqBD{{G6k3R$ zCyDV~CPy{{JQ5=dUO|dBx;RM+aDHKldx$0jyfT-MR8UahaKGp!(nLmK%s0c$7345` z)YaMDak-=Gbl=t&tHyO@PZFqg&kIY475za0$#bDdkk{*r6yRHa`i&+ra_QK9DU%+| zskHHaC?K|(CGu5CTX{ha_{Evb8%di5(oaqsWqhG&(-OI?w231pCzG!o!XvRo z560yYtol9hdl~b{d}sz1`^vIf;>2^GGFh+hRo1LeZ}p~XyOWhYsmh)e<1SOV(Yo5d z(!a@XO?@2N4DH&h*G5-I)rRm7tr+n9tvB2qt=RjXQeElKyK=sq<&_YFAk=;-LM5LoIrN7a%pJPExKxaX){U)Z_8 zrs@Wg4p++IS~2Z1j-B(J+f1j*bRKit2cU6^+8LV87&&~4nut@m6%NVR&jG0LPzM@f z54KN|9)W~!7IUn%uysOuz)sl8R32#@1_tsddE`BZP=u0{JG~OF; zY%^^t)Aq&N-{0H5=u$7bbOOB8vkt<~tFiP_J08D&iER&c?|4tYLDcv(H9oS7ZM~W} z#B7M1Eav2}NqQhf52)EmyB!ARoWvPpUrSCj4#@Dkt?f0ot5PZ9;?ak}1Q-v>Fk2@@Zc--h=Sf0V!0uRw@N Qz8s%x{)01irBdAHUp6Htw*UYD delta 252 zcmdmOwqK2JIWI340}$lDd7t6QvypENALHlEyZQPVC-36t4@qGRX3%7+()P_y&Q>T( z%*jkiEXmBzQ_xjN%qdT-ELJE;EG|whR!GY)QYc9+F3HSG_bYM*8mGxr!~>*m@us8} zXQt=HXXX`@mJ}B=0mUY(iMg^Cd4X7Uyc&})@Uu-;kc?mqn_MXwF5m|gE(!z@As`}b w@&idl#zm8vr0OS!NC{4!Eak}Wss{jB1 diff --git a/src/osbridge/backend.py b/src/osbridge/backend.py index 731d7856..11985f18 100644 --- a/src/osbridge/backend.py +++ b/src/osbridge/backend.py @@ -80,7 +80,38 @@ def output_values(self, flag): return [] def func_for_validation(self, design_inputs): - """Mock validation - always passes for testing""" + """Validate user inputs according to IRC 5 specifications""" + errors = [] + + # Validate Span + if KEY_SPAN in design_inputs: + try: + span = float(design_inputs[KEY_SPAN]) + if span < SPAN_MIN or span > SPAN_MAX: + errors.append(f"Span must be between {SPAN_MIN} m and {SPAN_MAX} m. Current value: {span} m") + except (ValueError, TypeError): + errors.append("Span must be a valid number") + + # Validate Carriageway Width + if KEY_CARRIAGEWAY_WIDTH in design_inputs: + try: + width = float(design_inputs[KEY_CARRIAGEWAY_WIDTH]) + if width < CARRIAGEWAY_WIDTH_MIN: + errors.append(f"Carriageway Width must be at least {CARRIAGEWAY_WIDTH_MIN} m as per IRC 5 Clause 104.3.1. Current value: {width} m") + except (ValueError, TypeError): + errors.append("Carriageway Width must be a valid number") + + # Validate Skew Angle + if KEY_SKEW_ANGLE in design_inputs: + try: + angle = float(design_inputs[KEY_SKEW_ANGLE]) + if angle < SKEW_ANGLE_MIN or angle > SKEW_ANGLE_MAX: + errors.append(f"Skew Angle should be between {SKEW_ANGLE_MIN}° and {SKEW_ANGLE_MAX}° as per IRC 5 Clause 105.3.3. Current value: {angle}°") + except (ValueError, TypeError): + errors.append("Skew Angle must be a valid number") + + if errors: + return "\n".join(errors) return None def get_3d_components(self): From 6fe64c107ac9e25ec290864794f85ce7037f5f2c Mon Sep 17 00:00:00 2001 From: Garvit S Rathore Date: Thu, 16 Oct 2025 01:12:59 +0530 Subject: [PATCH 10/59] Set validition limits and input values --- .../__pycache__/common.cpython-311.pyc | Bin 1908 -> 2655 bytes src/osbridge/common.py | 44 +++++++++++++++--- 2 files changed, 38 insertions(+), 6 deletions(-) diff --git a/src/osbridge/__pycache__/common.cpython-311.pyc b/src/osbridge/__pycache__/common.cpython-311.pyc index 3c4406d541d28c8b1d5c6803b96d653e2ba888cc..524081f27d9a13b400937329331aefc7d57ccc78 100644 GIT binary patch delta 1155 zcmZWo&u<$=6kfajv9__3xOUChw;)_dQ15;Iek2AI-XJ-se{@_2J& z>&clTA^Be4lPqKC_bnFyQ?@1Y#ulQ+He>T7M#nPX|;GD52hOklt3F< zY}h^Bf1-ogQF&ljw;EvZXq7ghrUUk6LcKkCOzRCsH!?akFZC8y^dGw!E>?k91Q_s8 zPKP!fQ4k9b(J5fsCu^9(VojaacxWFOyTm*EW16K2ji*(=0?`4U(gp| z&~~H>O`sc9%7D3b7#`T9T&yr^f`+lMj6I?}t^mO-@^>9kt}|*kfwdzgctNGHkg{Qb zK7wLN>;W}Cuv!ds9?n=4#kMVm{fkG4F-DT*TFAn}GFAEKwERMW@4xNG4p z!gx>fe!8LI?ZIbj=f>&ozj1`Au_Fj?Fxxx5pS^$hcM)Ff{dg`bj0`0|zePMPmv)&gHgURJ?bg=M2Uzi&t z9`%K(!Sqfq6kK^a6vOl0p)j8M!pv|{xSjR%;~5;?iQnSw{%CY3hNAG+WLF$SCc5G< eAj~fGg(Sw}$!=^gEiFpbdxtdm0V$O+0MVX1|spW~4 z3gMY4B^eNL(ctXVas|h{^qf=$jg-{%qSVx4O{>XSOq!0&Ra`##$%!SI`FRS4NNgh{ zwy`GjEf&B0ywoZV|GZR%;LMcNDlVsdpy@#7OV&vMy(poZbZn$s6KY7Z^lt z2uL^Z-jFrDz#w%)MD7BE;0-a|$wxWWlx|45T!0`3@f$)W7r>~2|AtW21u$yhpKQY= I&jPXy0Jc Date: Thu, 16 Oct 2025 01:14:48 +0530 Subject: [PATCH 11/59] Add project location handler and default values and tooltips for specific fields --- .../__pycache__/input_dock.cpython-311.pyc | Bin 20213 -> 24083 bytes src/osbridge/input_dock.py | 64 +++++++++++++++++- 2 files changed, 63 insertions(+), 1 deletion(-) diff --git a/src/osbridge/__pycache__/input_dock.cpython-311.pyc b/src/osbridge/__pycache__/input_dock.cpython-311.pyc index df76744065005ea16da2fc1efcb4031f38434fc5..ca476bb5a939dff2fd363a634acc68c84803cdd0 100644 GIT binary patch delta 5686 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 G=>Gr>+`=RP delta 2277 zcmah~du)?c6u-CM*LGd^==;4F?A6V69UB8W7+c0x*n=@ZHaccg_LZ&B2l@Iy?*tO^!`wLzp;_`}Dc}_aVQUeYzlFqN@+Z0|SQ)?s=T$MLRi5RpV6202We0Y_MWq9K z;4|fOxEN+RyL?FH#9if7r;LzaG%l8RHRh5+xLGBcXZ-PSm&(NK9I67?(`XGtlTQmK{dBh@WVUKmes66y2$*fVO#w0gtR3w zddL$DM&c1E7(7b&$p$dEeDXfmUzilgQ1Rz(6V=vJFcNSp*q#~9<0SiqkdzRY)4RW< z7SF;HB|WV0MiuZjXOh1wX~9NnMuwNdlhU%Zr>q0ZAeQxzb3Q1uE5?oTbk%7k$B(=z zSJWL>G;r~XRDsk>R2S$F%lD*~aJMh=5Xy4Jd(r@WTj!MT)~~@NCKU~Rq~zzGlO-8_alZaI;yzG5|GvN-I4BCshtOxnV@jzi>XKNp~=xNE)isTbNH?p9Ob< z-xt9Sxa{k}PPp&;8oT71EpCOmXk~#AH5*YQMGfB8t)!<-wO&%=3=H@yKo$7gtw@*%ffbjbJnc4HMQ zr+yghFHwI;bdtAo Date: Sat, 18 Oct 2025 18:37:31 +0530 Subject: [PATCH 12/59] Additional inputs --- .../additional_inputs.cpython-311.pyc | Bin 0 -> 31370 bytes .../__pycache__/backend.cpython-311.pyc | Bin 6971 -> 7297 bytes .../__pycache__/common.cpython-311.pyc | Bin 2655 -> 8361 bytes .../__pycache__/input_dock.cpython-311.pyc | Bin 24083 -> 26844 bytes src/osbridge/additional_inputs.py | 582 ++++++++++++++++++ src/osbridge/backend.py | 5 +- src/osbridge/common.py | 130 +++- src/osbridge/input_dock.py | 53 +- 8 files changed, 756 insertions(+), 14 deletions(-) create mode 100644 src/osbridge/__pycache__/additional_inputs.cpython-311.pyc create mode 100644 src/osbridge/additional_inputs.py diff --git a/src/osbridge/__pycache__/additional_inputs.cpython-311.pyc b/src/osbridge/__pycache__/additional_inputs.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..752a0dbf1e4b8ea242a4c2073e5724a3941d6eef GIT binary patch literal 31370 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 delta 459 zcmZp)+-=6UoR^o20SIKiyw3<1+Q_$^iBWFzF(z5o$*vsHjEb8VayT+EX$nlXVivIgk*!WQ_B?`^U`xt6^b+ROLI~t-w~Bm(g!Lzv_T=USfL=bNWnA6S;17n zIVZ8SI90*Wz*Nsz&v*AVuFxYBh!(&Kz1Zk98?Zj z;v)ltXfo3mFbNa~vL`Q;ybN?^sgx&MDcG5lk4QPIID?pOAi@(w_=1Q45Rn8TQb2_3 zWNqmHej`?y4-9Z3V{(^teT6GXnjb{)0*PN7Ho5sJr8%i~MOi>DBM=vZ%=^I1$jEqu bLF57qePAhJWEA?qfSvpZ7XN}n7Hk~=Kst8| diff --git a/src/osbridge/__pycache__/common.cpython-311.pyc b/src/osbridge/__pycache__/common.cpython-311.pyc index 524081f27d9a13b400937329331aefc7d57ccc78..52031f9cc91ad921560c9227716624dd5ba60bb8 100644 GIT binary patch literal 8361 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 KKk{N#;Qs*!;>}|K delta 458 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 QP3>cO^bh|B5jD;K0z09d!T4 zHLY4{(MZpxEN#;|(Uz&}R#o%**SaZ_rfuq^Wi*=BT3Z*b+SFEBLe@^}I;q>a@9`jz zM%zRFao;)jeB5)-J@>pntPyyVn7><8RKVd={ps!Fy20~iCwc39{qQp31WwS#OA?l_ zCBcVz`CS*cCTw9_!XCCK9ASq{)5l9goQ50V1jAWQ$P@FfYB}yz{3%b^DdeB!!Y;vx z=oShPJwhR3nJi;MnIciUL#A9PB9olo{2|jZ>NkU#yaLzARJL7XC!`1lwTC?w$a;mN zUi?SP88}ri!S}UI2ASc^zNdYY5DUDZ?<3{#C%s$08>d;|bN$oA3-4)N*_R9+GE~cC z>lxHB*u}uYpn*XnLW-@(=kv+7a@M#wD@CI5*j!Q+BBx@)oG8&YoKXha{AJ?JKAC@x zkk_-8f*%t@N3`p)Xct^FmBa1ASM{fk$0RX(*|er@Z9<8hJ|&%wiz$t)Ur}#~o;(>p z9hr@ZaUmkJ>3(F_!IhFGg5MflP;9hlBr9Cf zxpiqSQUGfanNWq2{OefVuSHKGhDap z;mJk^d}Oy3%6Z2oXDo1e#;)b&%~0uZDauMw8Lz0~I8>&NVa@1-Thlhe!+-1*@PUyp zk^JgBWhN?d7|!U@x=mM|Lgu z4R4M!9$bY3{#I%)+@h7q!KiZD@>QDb;Ql6FtIL#cEZc%(b%}2D&dGe+WEOGr_eO%x zT3oLYQUTZ8iy9h)sK-HSVad}^>fxuJy286s6mEOEd6u_+1NGoPI>_KK1j~Ze^2r#$ z_s99c7v*g}#%#>ht)IWH;*gOZq_QTA4x5~aRHO2wo!m=we%zfM`)4D{7ip*OR2 z1LSAf*XqKgj2(a(c7D_+9|tV?KK{Uwt=ToHg*O_T2+?u6_HC10ya zo25NF>8pfV(*?9CU7T^t-b@992BGjwfz$;zJ3abhWMD#}n;*73NP>!+CFzn(g`8!# zrg?2i#w(A{?S6pxSbFW<=60oOa=uC(-G^U|DeomUOodMB<~RswL7pe_sDKQA3cPrXF((%v;nCKWIFG{50`AW*K6j=@S-IlSD)lSX+xzijjEeq^UU z+LPw-ur;gnG(X>>jx)QN9F~Mu<-G8jHhJBVJ^aCE(k6kp)R~+lY!21?~=W;NMD8Z?uM#WZE`<`i>h|+3RJOB zdd7mmk~573r(4z-=wKRyca4=u1_D0PgSl9zGjh@suo7q_2XK-Nt_7^*ppp^+H#x)v zi3{AF<)oMKo%h+@DrIq74nM^lu2|J1tI*-6?{Qc|9rx_)v&?zj8D8E@b<5FFxDjw@ zO@bS4b=BHETl*lCoscU&ZF#N}D;$>@V1DaO0?t8q9N>49z&mZlrK>y5Qvn0S)@Cb_ zV2dez1JhC!e7noJODJa~^?894DlTcS5^Oi{r#ys1O>(AKz0$W-D#s~vA-=D&^!Y97 z#%UcvnPC>$E}!(`78hkIrI)t7S6$NI&t7Hq4%wQWvQXDu4Zq%7XxcD~NvfEwua&Os zFmvB!)z|$WbGNpg|-*j_r7f_ms+!rpLC2%Q5&k4sXEM7mqHcL|8pZ#{WdT2p2^$<0n4m!?2?(N3dNQaOskXyHt(WDrsK}N;yhogB6SocH&3|JSF zGxjgxG$EFpqg{;1`a`gf^bre8jSP?>xIA*saEx&ppkwsQ2Dt&<4;MzOmWP=jE{$0>`KcS@q&F=?p$*^Au2`vF6zntWIRL+a?>yYr4wGm`#3Y@&h|_! zlkD#%i%7mijEBe_PRf21`kn?}d^iAC9<9huKGLjdAJP1SE)bx{Pbi@5Ib9rN_`7dRK6rwN0lqu;tVXts1<2)mql6A zl{2tJ#+FD5 a2fL2PdLw#*3;AMP&ATIWKDiHHHuPT#&qNOZ diff --git a/src/osbridge/additional_inputs.py b/src/osbridge/additional_inputs.py new file mode 100644 index 00000000..01507e30 --- /dev/null +++ b/src/osbridge/additional_inputs.py @@ -0,0 +1,582 @@ +""" +Additional Inputs Widget for Highway Bridge Design +Provides detailed input fields for manual bridge parameter definition +""" +import sys +from PySide6.QtWidgets import ( + QWidget, QVBoxLayout, QHBoxLayout, QTabWidget, QLabel, QLineEdit, + QComboBox, QGroupBox, QFormLayout, QPushButton, QScrollArea, + QCheckBox, QMessageBox, QSizePolicy, QSpacerItem +) +from PySide6.QtCore import Qt, Signal +from PySide6.QtGui import QDoubleValidator, QIntValidator + +from common import * + + +class OptimizableField(QWidget): + """Widget that allows selection between Optimized/Customized/All modes with input field""" + + def __init__(self, label_text, parent=None): + super().__init__(parent) + self.layout = QHBoxLayout(self) + self.layout.setContentsMargins(0, 0, 0, 0) + + # Mode selector + self.mode_combo = QComboBox() + self.mode_combo.addItems(VALUES_OPTIMIZATION_MODE) + self.mode_combo.setMaximumWidth(120) + + # Input field + self.input_field = QLineEdit() + self.input_field.setEnabled(False) # Disabled by default for "Optimized" + + self.layout.addWidget(self.mode_combo) + self.layout.addWidget(self.input_field) + + # Connect signal + self.mode_combo.currentTextChanged.connect(self.on_mode_changed) + + def on_mode_changed(self, text): + """Enable/disable input field based on selection""" + if text == "Optimized": + self.input_field.setEnabled(False) + self.input_field.clear() + else: + self.input_field.setEnabled(True) + + def get_value(self): + """Returns tuple of (mode, value)""" + return (self.mode_combo.currentText(), self.input_field.text()) + + +class BridgeGeometryTab(QWidget): + """Sub-tab for Bridge Geometry inputs""" + + footpath_changed = Signal(str) # Signal when footpath status changes + + def __init__(self, footpath_value="None", parent=None): + super().__init__(parent) + self.footpath_value = footpath_value + self.init_ui() + + def style_input_field(self, field): + """Apply consistent styling to input fields""" + field.setMinimumHeight(28) + if isinstance(field, QComboBox): + field.setStyleSheet(""" + QComboBox { + padding: 4px 8px; + border: 1px solid #c0c0c0; + border-radius: 3px; + background-color: white; + color: #333; + } + QComboBox::drop-down { + subcontrol-origin: padding; + subcontrol-position: top right; + width: 20px; + border-left: 1px solid #c0c0c0; + background-color: #e8e8e8; + } + QComboBox::down-arrow { + image: none; + border-left: 4px solid transparent; + border-right: 4px solid transparent; + border-top: 6px solid #606060; + margin-right: 5px; + } + QComboBox:focus { + border: 1px solid #4a7ba7; + } + QComboBox QAbstractItemView { + background-color: white; + border: 1px solid #c0c0c0; + outline: none; + selection-background-color: #e8f4ff; + } + QComboBox QAbstractItemView::item { + color: black; + background-color: white; + padding: 4px; + min-height: 20px; + } + QComboBox QAbstractItemView::item:hover { + background-color: #e8f4ff; + color: black; + } + """) + else: + field.setStyleSheet(""" + QLineEdit { + padding: 4px 8px; + border: 1px solid #c0c0c0; + border-radius: 3px; + background-color: white; + color: #333; + } + QLineEdit:focus { + border: 1px solid #4a7ba7; + } + QLineEdit:disabled { + background-color: #f0f0f0; + color: #999; + } + """) + + def style_group_box(self, group_box): + """Apply consistent styling to group boxes""" + group_box.setStyleSheet(""" + QGroupBox { + font-weight: bold; + border: 2px solid #d0d0d0; + border-radius: 6px; + margin-top: 12px; + padding-top: 15px; + background-color: #f9f9f9; + } + QGroupBox::title { + subcontrol-origin: margin; + subcontrol-position: top left; + left: 10px; + padding: 0 5px; + background-color: white; + color: #4a7ba7; + } + """) + + def init_ui(self): + main_layout = QVBoxLayout(self) + main_layout.setContentsMargins(10, 10, 10, 10) + + # Main horizontal split: Diagram (left) and Inputs (right) + content_layout = QHBoxLayout() + + # LEFT SIDE: Diagram + diagram_widget = QWidget() + diagram_widget.setStyleSheet(""" + QWidget { + background-color: white; + border: 2px solid #d0d0d0; + border-radius: 4px; + } + """) + diagram_widget.setMinimumWidth(450) + diagram_widget.setMaximumWidth(500) + diagram_layout = QVBoxLayout(diagram_widget) + diagram_layout.setContentsMargins(10, 10, 10, 10) + + # Diagram image placeholder + diagram_label = QLabel() + diagram_label.setText("Bridge Geometry Diagram\n(Placeholder)") + diagram_label.setAlignment(Qt.AlignCenter) + diagram_label.setStyleSheet(""" + QLabel { + background-color: #f5f5f5; + border: 1px dashed #999; + padding: 20px; + font-size: 12px; + color: #666; + min-height: 300px; + } + """) + diagram_layout.addWidget(diagram_label) + + content_layout.addWidget(diagram_widget) + + # RIGHT SIDE: Input Fields with Scroll + input_scroll = QScrollArea() + input_scroll.setWidgetResizable(True) + input_scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + input_scroll.setStyleSheet(""" + QScrollArea { + border: none; + background-color: white; + } + """) + + input_container = QWidget() + input_container.setStyleSheet("background-color: white; color: #000000;") + inputs_layout = QVBoxLayout(input_container) + inputs_layout.setContentsMargins(15, 10, 15, 10) + inputs_layout.setSpacing(12) + + # GROUP 1: Girder Spacing, No. of Girders, Deck Overhang Width + girder_group = QGroupBox("") + self.style_group_box(girder_group) + girder_layout = QFormLayout(girder_group) + girder_layout.setSpacing(8) + girder_layout.setLabelAlignment(Qt.AlignLeft | Qt.AlignVCenter) + + self.girder_spacing = QLineEdit() + self.girder_spacing.setValidator(QDoubleValidator(0.0, 10.0, 2)) + self.girder_spacing.setPlaceholderText("Enter spacing in meters") + self.style_input_field(self.girder_spacing) + girder_layout.addRow("Girder Spacing (m):", self.girder_spacing) + + self.no_of_girders = QLineEdit() + self.no_of_girders.setEnabled(False) + self.no_of_girders.setPlaceholderText("Auto-calculated") + self.style_input_field(self.no_of_girders) + girder_layout.addRow("No. of Girders:", self.no_of_girders) + + self.deck_overhang = QLineEdit() + self.deck_overhang.setValidator(QDoubleValidator(0.0, 5.0, 2)) + self.deck_overhang.setPlaceholderText("Enter overhang width") + self.style_input_field(self.deck_overhang) + girder_layout.addRow("Deck Overhang Width (m):", self.deck_overhang) + + inputs_layout.addWidget(girder_group) + + # GROUP 2: Deck Thickness + deck_group = QGroupBox("") + self.style_group_box(deck_group) + deck_layout = QFormLayout(deck_group) + deck_layout.setSpacing(8) + deck_layout.setLabelAlignment(Qt.AlignLeft | Qt.AlignVCenter) + + self.deck_thickness = QLineEdit() + self.deck_thickness.setValidator(QDoubleValidator(0.0, 500.0, 0)) + self.deck_thickness.setPlaceholderText("Enter thickness in mm") + self.style_input_field(self.deck_thickness) + deck_layout.addRow("Deck Thickness (mm):", self.deck_thickness) + + inputs_layout.addWidget(deck_group) + + # GROUP 3: Footpath Width and Thickness + self.footpath_group = QGroupBox("") + self.style_group_box(self.footpath_group) + self.footpath_group.setVisible(self.footpath_value != "None") + footpath_layout = QFormLayout(self.footpath_group) + footpath_layout.setSpacing(8) + footpath_layout.setLabelAlignment(Qt.AlignLeft | Qt.AlignVCenter) + + self.footpath_width = QLineEdit() + self.footpath_width.setValidator(QDoubleValidator(0.0, 5.0, 2)) + self.footpath_width.setPlaceholderText("Enter width in meters") + self.style_input_field(self.footpath_width) + footpath_layout.addRow("Footpath Width (m):", self.footpath_width) + + self.footpath_thickness = QLineEdit() + self.footpath_thickness.setValidator(QDoubleValidator(0.0, 500.0, 0)) + self.footpath_thickness.setPlaceholderText("Auto-fills from deck thickness") + self.style_input_field(self.footpath_thickness) + footpath_layout.addRow("Footpath Thickness (mm):", self.footpath_thickness) + + inputs_layout.addWidget(self.footpath_group) + + # GROUP 4: Crash Barrier Details, Railing Details, Safety Kerb (Combined) + combined_group = QGroupBox("") + self.style_group_box(combined_group) + combined_layout = QVBoxLayout(combined_group) + combined_layout.setSpacing(10) + + # Crash Barrier Details Section + crash_barrier_label = QLabel("Crash Barrier Details:") + crash_barrier_label.setStyleSheet("font-weight: bold; font-size: 11px; margin-bottom: 5px; color: #4a7ba7;") + combined_layout.addWidget(crash_barrier_label) + + crash_form = QFormLayout() + crash_form.setSpacing(8) + crash_form.setLabelAlignment(Qt.AlignLeft | Qt.AlignVCenter) + + self.crash_barrier_type = QComboBox() + self.style_input_field(self.crash_barrier_type) + self.crash_barrier_type.addItems(VALUES_CRASH_BARRIER_TYPE) + self.crash_barrier_type.currentTextChanged.connect(self.on_crash_barrier_type_changed) + crash_form.addRow("Type:", self.crash_barrier_type) + + self.crash_barrier_density = QLineEdit() + self.style_input_field(self.crash_barrier_density) + self.crash_barrier_density.setPlaceholderText("Material density") + self.crash_barrier_density.setValidator(QDoubleValidator(0.0, 100.0, 2)) + crash_form.addRow("Material Density (MPa):", self.crash_barrier_density) + + self.crash_barrier_width = QLineEdit() + self.style_input_field(self.crash_barrier_width) + self.crash_barrier_width.setPlaceholderText("Barrier width") + self.crash_barrier_width.setValidator(QDoubleValidator(0.0, 2.0, 3)) + crash_form.addRow("Width (m):", self.crash_barrier_width) + + self.crash_barrier_area = QLineEdit() + self.style_input_field(self.crash_barrier_area) + self.crash_barrier_area.setPlaceholderText("Cross-sectional area") + self.crash_barrier_area.setValidator(QDoubleValidator(0.0, 10.0, 4)) + crash_form.addRow("Area (m²):", self.crash_barrier_area) + + combined_layout.addLayout(crash_form) + + # Railing Details Section + railing_label = QLabel("Railing Details:") + railing_label.setStyleSheet("font-weight: bold; font-size: 11px; margin-top: 10px; margin-bottom: 5px; color: #4a7ba7;") + combined_layout.addWidget(railing_label) + + railing_form = QFormLayout() + railing_form.setSpacing(8) + railing_form.setLabelAlignment(Qt.AlignLeft | Qt.AlignVCenter) + + self.railing_width = QLineEdit() + self.railing_width.setValidator(QDoubleValidator(0.0, 1000.0, 0)) + self.railing_width.setPlaceholderText("Enter width in mm") + self.style_input_field(self.railing_width) + railing_form.addRow("Width (m):", self.railing_width) + + self.railing_height = QLineEdit() + self.railing_height.setValidator(QDoubleValidator(0.0, 3000.0, 0)) + self.railing_height.setPlaceholderText("Enter height in mm") + self.style_input_field(self.railing_height) + railing_form.addRow("Height (m):", self.railing_height) + + combined_layout.addLayout(railing_form) + + # Safety Kerb Section (conditional) + self.safety_kerb_container = QWidget() + self.safety_kerb_container.setVisible(self.footpath_value == "None") + safety_kerb_layout = QVBoxLayout(self.safety_kerb_container) + safety_kerb_layout.setContentsMargins(0, 0, 0, 0) + safety_kerb_layout.setSpacing(5) + + safety_kerb_label = QLabel("Safety Kerb:") + safety_kerb_label.setStyleSheet("font-weight: bold; font-size: 11px; margin-top: 10px; margin-bottom: 5px; color: #4a7ba7;") + safety_kerb_layout.addWidget(safety_kerb_label) + + safety_kerb_form = QFormLayout() + safety_kerb_form.setSpacing(8) + safety_kerb_form.setLabelAlignment(Qt.AlignLeft | Qt.AlignVCenter) + + self.safety_kerb_width = QLineEdit() + self.style_input_field(self.safety_kerb_width) + self.safety_kerb_width.setPlaceholderText("Min 750mm if no footpath") + self.safety_kerb_width.setValidator(QDoubleValidator(0.0, 2.0, 3)) + safety_kerb_form.addRow("Width (m):", self.safety_kerb_width) + + self.safety_kerb_thickness = QLineEdit() + self.style_input_field(self.safety_kerb_thickness) + self.safety_kerb_thickness.setPlaceholderText("Enter thickness in mm") + self.safety_kerb_thickness.setValidator(QDoubleValidator(0.0, 500.0, 0)) + safety_kerb_form.addRow("Thickness (mm):", self.safety_kerb_thickness) + + safety_kerb_layout.addLayout(safety_kerb_form) + combined_layout.addWidget(self.safety_kerb_container) + + inputs_layout.addWidget(combined_group) + + inputs_layout.addStretch() + + input_scroll.setWidget(input_container) + content_layout.addWidget(input_scroll, 1) + + main_layout.addLayout(content_layout) + + # Connect calculation signals + self.girder_spacing.textChanged.connect(self.calculate_no_of_girders) + self.deck_overhang.textChanged.connect(self.calculate_no_of_girders) + self.deck_thickness.textChanged.connect(self.update_footpath_thickness) + + def update_footpath_value(self, footpath_value): + """Update visibility based on footpath selection""" + self.footpath_value = footpath_value + self.footpath_group.setVisible(footpath_value != "None") + self.safety_kerb_container.setVisible(footpath_value == "None") + self.footpath_changed.emit(footpath_value) + + def calculate_no_of_girders(self): + """Calculate number of girders based on spacing and overhang""" + # This would need bridge width from main inputs + # Placeholder calculation + try: + spacing = float(self.girder_spacing.text()) if self.girder_spacing.text() else 0 + overhang = float(self.deck_overhang.text()) if self.deck_overhang.text() else 0 + if spacing > 0: + # Placeholder: assume bridge width of 10m for demo + bridge_width = 10.0 + no_girders = int((bridge_width - 2 * overhang) / spacing) + 1 + self.no_of_girders.setText(str(no_girders)) + except: + pass + + def update_footpath_thickness(self): + """Pre-fill footpath thickness with deck thickness""" + if self.deck_thickness.text() and not self.footpath_thickness.text(): + self.footpath_thickness.setText(self.deck_thickness.text()) + + def on_crash_barrier_type_changed(self, barrier_type): + """Warn if flexible/semi-rigid barrier without footpath""" + if (barrier_type in ["Flexible", "Semi-Rigid"]) and (self.footpath_value == "None"): + QMessageBox.warning( + self, + "Crash Barrier Type Not Permitted", + f"{barrier_type} crash barriers are not permitted on bridges without an outer footpath per IRC 5 Clause 109.6.4.", + QMessageBox.Ok + ) + + +class AdditionalInputsWidget(QWidget): + """Main widget for Additional Inputs with tabbed interface""" + + def __init__(self, footpath_value="None", parent=None): + super().__init__(parent) + self.footpath_value = footpath_value + self.init_ui() + + def init_ui(self): + main_layout = QVBoxLayout(self) + main_layout.setContentsMargins(5, 5, 5, 5) + + # Title + title = QLabel("Additional Inputs") + title.setStyleSheet("font-size: 14px; font-weight: bold; padding: 5px;") + main_layout.addWidget(title) + + # Main tab widget + self.tabs = QTabWidget() + self.tabs.setStyleSheet(""" + QTabWidget::pane { + border: 1px solid #d0d0d0; + background: white; + } + QTabBar::tab { + background: #f0f0f0; + color: black; + border: 1px solid #d0d0d0; + padding: 8px 16px; + margin-right: 2px; + } + QTabBar::tab:selected { + background: #A2EBB6; + color: black; + border-bottom-color: white; + } + """) + + # Sub-Tab 1: Bridge Geometry + self.bridge_geometry_tab = BridgeGeometryTab(self.footpath_value) + self.tabs.addTab(self.bridge_geometry_tab, "Bridge Geometry") + + # Sub-Tab 2: Section Properties + section_props_tab = self.create_placeholder_tab( + "Section Properties", + "This tab will contain:\n\n" + + "• Girder Details (IS Standard Rolled Beam / Plate Girder)\n" + + "• Stiffener Details\n" + + "• Cross-Bracing Details\n" + + "• End Diaphragm Details\n\n" + + "Implementation in progress..." + ) + self.tabs.addTab(section_props_tab, "Section Properties") + + # Sub-Tab 3: Dead Load Inputs + dead_load_tab = self.create_placeholder_tab( + "Dead Load Inputs", + "This tab will contain:\n\n" + + "• Self Weight (with factor)\n" + + "• Superimposed Load (Wearing Coat)\n" + + "• Load from Railing\n" + + "• Load from Crash Barrier\n" + + "• Additional Superimposed Load (Pressure/Line/Point)\n\n" + + "Implementation in progress..." + ) + self.tabs.addTab(dead_load_tab, "Dead Load Inputs") + + # Sub-Tab 4: Live Load Inputs + live_load_tab = self.create_placeholder_tab( + "Live Load Inputs", + "This tab will contain:\n\n" + + "• Standard IRC Vehicles (Class A, 70R, AA, SV)\n" + + "• Custom Vehicle Configuration\n" + + "• Footpath Pressure (Automatic/User-defined)\n\n" + + "Implementation in progress..." + ) + self.tabs.addTab(live_load_tab, "Live Load Inputs") + + # Sub-Tab 5: Lateral Load + lateral_load_tab = self.create_placeholder_tab( + "Lateral Load", + "Lateral load definition is under development.\n\n" + + "This will include wind loads, seismic loads, and other\n" + + "lateral forces acting on the bridge structure." + ) + self.tabs.addTab(lateral_load_tab, "Lateral Load") + + # Sub-Tab 6: Support Conditions + support_tab = self.create_placeholder_tab( + "Support Conditions", + "This tab will contain:\n\n" + + "• Left Support (Fixed/Pinned)\n" + + "• Right Support (Fixed/Pinned)\n" + + "• Bearing Length (mm)\n\n" + + "Note: If bearing length is 0, the end bearing\n" + + "stiffener will not be designed.\n\n" + + "Implementation in progress..." + ) + self.tabs.addTab(support_tab, "Support Conditions") + + main_layout.addWidget(self.tabs) + + # Close button + close_btn = QPushButton("Close") + close_btn.clicked.connect(self.close) + close_btn.setMaximumWidth(100) + btn_layout = QHBoxLayout() + btn_layout.addStretch() + btn_layout.addWidget(close_btn) + main_layout.addLayout(btn_layout) + + def create_placeholder_tab(self, title, description): + """Create a styled placeholder tab with title and description""" + widget = QWidget() + widget.setStyleSheet("background-color: white;") + + layout = QVBoxLayout(widget) + layout.setAlignment(Qt.AlignCenter) + layout.setContentsMargins(40, 40, 40, 40) + + # Icon or visual indicator + icon_label = QLabel("🚧") + icon_label.setStyleSheet("font-size: 48px;") + icon_label.setAlignment(Qt.AlignCenter) + layout.addWidget(icon_label) + + # Title + title_label = QLabel(title) + title_label.setStyleSheet(""" + font-size: 18px; + font-weight: bold; + color: #333; + margin-top: 20px; + margin-bottom: 10px; + """) + title_label.setAlignment(Qt.AlignCenter) + layout.addWidget(title_label) + + # Status + status_label = QLabel("Under Development") + status_label.setStyleSheet(""" + font-size: 14px; + color: #f39c12; + font-weight: bold; + margin-bottom: 20px; + """) + status_label.setAlignment(Qt.AlignCenter) + layout.addWidget(status_label) + + # Description + desc_label = QLabel(description) + desc_label.setStyleSheet(""" + font-size: 12px; + color: #666; + line-height: 1.6; + """) + desc_label.setAlignment(Qt.AlignCenter) + desc_label.setWordWrap(True) + desc_label.setMaximumWidth(600) + layout.addWidget(desc_label) + + layout.addStretch() + + return widget + + def update_footpath_value(self, footpath_value): + """Update footpath value across all tabs""" + self.footpath_value = footpath_value + self.bridge_geometry_tab.update_footpath_value(footpath_value) diff --git a/src/osbridge/backend.py b/src/osbridge/backend.py index 11985f18..9bf499d5 100644 --- a/src/osbridge/backend.py +++ b/src/osbridge/backend.py @@ -61,6 +61,9 @@ def input_values(self): t14 = (KEY_DECK, KEY_DISP_DECK, TYPE_COMBOBOX, connectdb("Material"), True, 'No Validator') options_list.append(t14) + t15 = (KEY_DECK_CONCRETE_GRADE_BASIC, KEY_DISP_DECK_CONCRETE_GRADE, TYPE_COMBOBOX, VALUES_DECK_CONCRETE_GRADE, True, 'No Validator') + options_list.append(t15) + return options_list def customized_input(self): @@ -106,7 +109,7 @@ def func_for_validation(self, design_inputs): try: angle = float(design_inputs[KEY_SKEW_ANGLE]) if angle < SKEW_ANGLE_MIN or angle > SKEW_ANGLE_MAX: - errors.append(f"Skew Angle should be between {SKEW_ANGLE_MIN}° and {SKEW_ANGLE_MAX}° as per IRC 5 Clause 105.3.3. Current value: {angle}°") + errors.append(f"Skew Angle must be between {SKEW_ANGLE_MIN}° and {SKEW_ANGLE_MAX}°. IRC 24 (2010) requires detailed analysis when skew angle exceeds ±15 degrees. No calculations will be performed. Current value: {angle}°") except (ValueError, TypeError): errors.append("Skew Angle must be a valid number") diff --git a/src/osbridge/common.py b/src/osbridge/common.py index 8f3e47fa..ce2f8de2 100644 --- a/src/osbridge/common.py +++ b/src/osbridge/common.py @@ -17,6 +17,7 @@ KEY_GIRDER = "Girder" KEY_CROSS_BRACING = "Cross Bracing" KEY_DECK = "Deck" +KEY_DECK_CONCRETE_GRADE_BASIC = "Deck Concrete Grade" # Display names KEY_DISP_FINPLATE = "Highway Bridge Design" @@ -27,12 +28,13 @@ DISP_TITLE_GEOMETRIC = "Geometric Details" KEY_DISP_SPAN = "Span (m)* [20-45]" KEY_DISP_CARRIAGEWAY_WIDTH = "Carriageway Width (m)* [≥4.25]" -KEY_DISP_FOOTPATH = "Footpath (Default: None)" -KEY_DISP_SKEW_ANGLE = "Skew Angle (degrees) [≤30, Default: 0]" +KEY_DISP_FOOTPATH = "Footpath" +KEY_DISP_SKEW_ANGLE = "Skew Angle (degrees) [±15]" DISP_TITLE_MATERIAL = "Material Inputs" KEY_DISP_GIRDER = "Girder" KEY_DISP_CROSS_BRACING = "Cross Bracing" KEY_DISP_DECK = "Deck" +KEY_DISP_DECK_CONCRETE_GRADE = "Deck Concrete Grade [M25+]" # Sample values # Type of Structure: Defines the application of the steel girder bridge @@ -53,10 +55,10 @@ "Custom" # Allow custom location entry ] -# Footpath: Left or right edge or none or both +# Footpath: Single sided or none or both # Default: None # Note: IRC 5 Clause 101.41 requires safety kerb when footpath is not present -VALUES_FOOTPATH = ["None", "Left Edge", "Right Edge", "Both Edges"] +VALUES_FOOTPATH = ["None", "Single Sided", "Both"] VALUES_MATERIAL = ["E250 (Fe 410W)A", "E300 (Fe 440)", "E350 (Fe 490)", "E410 (Fe 540)", "E450 (Fe 570)", "E550 (Fe 650)"] @@ -68,12 +70,126 @@ # Carriageway Width: Minimum 4.25 m as per IRC 5 Clause 104.3.1 CARRIAGEWAY_WIDTH_MIN = 4.25 -# Skew Angle: IRC 5 Clause 105.3.3 recommends <= 30 degrees +# Skew Angle: IRC 24 (2010) requires detailed analysis when skew angle exceeds ±15 degrees # Default: 0 degrees -SKEW_ANGLE_MIN = 0.0 -SKEW_ANGLE_MAX = 30.0 +SKEW_ANGLE_MIN = -15.0 +SKEW_ANGLE_MAX = 15.0 SKEW_ANGLE_DEFAULT = 0.0 +# ===== Additional Inputs Constants ===== + +# Bridge Geometry Keys +KEY_GIRDER_SPACING = "Girder Spacing" +KEY_DECK_OVERHANG = "Deck Overhang Width" +KEY_NO_OF_GIRDERS = "No. of Girders" +KEY_DECK_THICKNESS = "Deck Thickness" +KEY_DECK_CONCRETE_GRADE = "Deck Concrete Grade" +KEY_DECK_REINF_MATERIAL = "Deck Reinforcement Material" +KEY_DECK_REINF_SIZE = "Deck Reinforcement Size" +KEY_DECK_REINF_SPACING_LONG = "Deck Reinforcement Spacing Longitudinal" +KEY_DECK_REINF_SPACING_TRANS = "Deck Reinforcement Spacing Transverse" +KEY_FOOTPATH_WIDTH = "Footpath Width" +KEY_FOOTPATH_THICKNESS = "Footpath Thickness" +KEY_RAILING_PRESENT = "Railing Present" +KEY_RAILING_WIDTH = "Railing Width" +KEY_RAILING_HEIGHT = "Railing Height" +KEY_SAFETY_KERB_PRESENT = "Safety Kerb Present" +KEY_SAFETY_KERB_WIDTH = "Safety Kerb Width" +KEY_SAFETY_KERB_THICKNESS = "Safety Kerb Thickness" +KEY_CRASH_BARRIER_PRESENT = "Crash Barrier Present" +KEY_CRASH_BARRIER_TYPE = "Crash Barrier Type" +KEY_CRASH_BARRIER_DENSITY = "Crash Barrier Material Density" +KEY_CRASH_BARRIER_WIDTH = "Crash Barrier Width" +KEY_CRASH_BARRIER_AREA = "Crash Barrier Area" + +# Section Properties Keys +KEY_GIRDER_TYPE = "Girder Type" +KEY_GIRDER_IS_SECTION = "Girder IS Section" +KEY_GIRDER_SYMMETRY = "Girder Symmetry" +KEY_GIRDER_TOP_FLANGE_WIDTH = "Girder Top Flange Width" +KEY_GIRDER_TOP_FLANGE_THICKNESS = "Girder Top Flange Thickness" +KEY_GIRDER_BOTTOM_FLANGE_WIDTH = "Girder Bottom Flange Width" +KEY_GIRDER_BOTTOM_FLANGE_THICKNESS = "Girder Bottom Flange Thickness" +KEY_GIRDER_DEPTH = "Girder Depth" +KEY_GIRDER_WEB_THICKNESS = "Girder Web Thickness" +KEY_GIRDER_TORSIONAL_RESTRAINT = "Torsional Restraint" +KEY_GIRDER_WARPING_RESTRAINT = "Warping Restraint" +KEY_GIRDER_WEB_TYPE = "Web Type" + +KEY_STIFFENER_DESIGN_METHOD = "Stiffener Design Method" +KEY_STIFFENER_PLATE_THICKNESS = "Stiffener Plate Thickness" +KEY_STIFFENER_SPACING = "Stiffener Spacing" +KEY_LONGITUDINAL_STIFFENER = "Longitudinal Stiffener" +KEY_LONGITUDINAL_STIFFENER_THICKNESS = "Longitudinal Stiffener Thickness" + +KEY_CROSS_BRACING_TYPE = "Cross Bracing Type" +KEY_CROSS_BRACING_SECTION = "Cross Bracing Section" +KEY_BRACKET_SECTION = "Bracket Section" +KEY_CROSS_BRACING_SPACING = "Cross Bracing Spacing" + +KEY_END_DIAPHRAGM_TYPE = "End Diaphragm Type" +KEY_END_DIAPHRAGM_SECTION = "End Diaphragm Section" +KEY_END_DIAPHRAGM_SPACING = "End Diaphragm Spacing" + +# Dead Load Keys +KEY_SELF_WEIGHT = "Self Weight" +KEY_SELF_WEIGHT_FACTOR = "Self Weight Factor" +KEY_WEARING_COAT_MATERIAL = "Wearing Coat Material" +KEY_WEARING_COAT_DENSITY = "Wearing Coat Density" +KEY_WEARING_COAT_THICKNESS = "Wearing Coat Thickness" +KEY_RAILING_LOAD_COUNT = "No. of Railings" +KEY_RAILING_LOAD = "Railing Load" +KEY_RAILING_LOAD_LOCATION = "Railing Load Location" +KEY_CRASH_BARRIER_LOAD_COUNT = "No. of Crash Barriers" +KEY_CRASH_BARRIER_LOAD = "Crash Barrier Load" +KEY_CRASH_BARRIER_LOAD_LOCATION = "Crash Barrier Load Location" + +# Live Load Keys +KEY_IRC_CLASS_A = "IRC Class A" +KEY_IRC_CLASS_70R = "IRC Class 70R" +KEY_IRC_CLASS_AA = "IRC Class AA" +KEY_IRC_CLASS_SV = "IRC Class SV" +KEY_CUSTOM_VEHICLE = "Custom Vehicle" +KEY_CUSTOM_AXLE_TYPE = "Custom Axle Type" +KEY_CUSTOM_NO_AXLES = "Custom Number of Axles" +KEY_CUSTOM_AXLE_LOAD = "Custom Axle Load" +KEY_CUSTOM_AXLE_SPACING = "Custom Axle Spacing" +KEY_CUSTOM_VEHICLE_SPACING = "Custom Vehicle Spacing" +KEY_CUSTOM_ECCENTRICITY = "Custom Eccentricity" +KEY_FOOTPATH_PRESSURE = "Footpath Pressure" +KEY_FOOTPATH_PRESSURE_VALUE = "Footpath Pressure Value" + +# Support Condition Keys +KEY_LEFT_SUPPORT = "Left Support" +KEY_RIGHT_SUPPORT = "Right Support" +KEY_BEARING_LENGTH = "Bearing Length" + +# Value Lists for Additional Inputs +VALUES_YES_NO = ["No", "Yes"] +VALUES_DECK_CONCRETE_GRADE = ["M25", "M30", "M35", "M40", "M45", "M50", "M55", "M60"] +VALUES_REINF_MATERIAL = ["Fe 415", "Fe 500", "Fe 550"] +VALUES_REINF_SIZE = ["8", "10", "12", "16", "20", "25", "32"] +VALUES_CRASH_BARRIER_TYPE = ["Rigid", "Semi-Rigid", "Flexible", "Other"] +VALUES_GIRDER_TYPE = ["IS Standard Rolled Beam", "Plate Girder"] +VALUES_GIRDER_SYMMETRY = ["Symmetrical", "Unsymmetrical"] +VALUES_OPTIMIZATION_MODE = ["Optimized", "Customized", "All"] +VALUES_TORSIONAL_RESTRAINT = ["Fully Restrained", "Partially Restrained - Support Connect", "Partially Restrained - Bearing Support"] +VALUES_WARPING_RESTRAINT = ["Both Flange Restraint", "No Restraint"] +VALUES_WEB_TYPE = ["Thin Web", "Thick Web"] +VALUES_STIFFENER_DESIGN = ["Simple Post", "Tension Field"] +VALUES_CROSS_BRACING_TYPE = ["K-bracing", "K-bracing with top bracket", "X-bracing"] +VALUES_END_DIAPHRAGM_TYPE = ["Same as cross-bracing", "Rolled Beam Section", "Plate Girder Section"] +VALUES_WEARING_COAT_MATERIAL = ["Concrete", "Bituminous", "Other"] +VALUES_CUSTOM_AXLE_TYPE = ["Single", "Bogie"] +VALUES_FOOTPATH_PRESSURE_MODE = ["Automatic", "User-defined"] +VALUES_SUPPORT_TYPE = ["Fixed", "Pinned"] + +# Default values +DEFAULT_SELF_WEIGHT_FACTOR = 1.0 +DEFAULT_CONCRETE_DENSITY = 25.0 # kN/m³ +DEFAULT_STEEL_DENSITY = 78.5 # kN/m³ +DEFAULT_BEARING_LENGTH = 0.0 # mm + def connectdb(table_name, popup=None): """Mock database connection - returns sample data""" if table_name == "Material": diff --git a/src/osbridge/input_dock.py b/src/osbridge/input_dock.py index 927a8440..d7315e16 100644 --- a/src/osbridge/input_dock.py +++ b/src/osbridge/input_dock.py @@ -1,12 +1,13 @@ import sys from PySide6.QtWidgets import ( QApplication, QWidget, QHBoxLayout, QVBoxLayout, QPushButton, - QComboBox, QScrollArea, QLabel, QFormLayout, QLineEdit, QGroupBox, QSizePolicy, QMessageBox, QInputDialog + QComboBox, QScrollArea, QLabel, QFormLayout, QLineEdit, QGroupBox, QSizePolicy, QMessageBox, QInputDialog, QDialog ) from PySide6.QtCore import Qt, QRegularExpression from PySide6.QtGui import QPixmap, QDoubleValidator, QRegularExpressionValidator from common import * +from additional_inputs import AdditionalInputsWidget class NoScrollComboBox(QComboBox): @@ -55,13 +56,13 @@ def apply_field_style(widget): subcontrol-position: top right; width: 20px; border-left: 1px solid #c0c0c0; - background-color: #4a7ba7; + background-color: #e8e8e8; } QComboBox::down-arrow { image: none; border-left: 4px solid transparent; border-right: 4px solid transparent; - border-top: 6px solid white; + border-top: 6px solid #606060; margin-right: 5px; } QComboBox QAbstractItemView { @@ -127,6 +128,8 @@ def __init__(self, backend, parent): self.structure_type_combo = None # Store reference to structure type combo box self.project_location_combo = None # Store reference to project location combo box self.custom_location_input = None # Store reference to custom location text field + self.footpath_combo = None # Store reference to footpath combo box + self.additional_inputs_window = None # Store reference to additional inputs window self.setStyleSheet("background: transparent;") self.main_layout = QHBoxLayout(self) @@ -262,6 +265,7 @@ def build_left_panel(self, field_list): } """) additional_inputs_btn.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) + additional_inputs_btn.clicked.connect(self.show_additional_inputs) top_bar.addWidget(additional_inputs_btn) panel_layout.addLayout(top_bar) @@ -371,9 +375,17 @@ def build_left_panel(self, field_list): # Set default value for Footpath combo box elif field[0] == KEY_FOOTPATH: - # Set default to "None" (first item in VALUES_FOOTPATH) + # Store reference and set default to "None" (first item in VALUES_FOOTPATH) + self.footpath_combo = right right.setCurrentIndex(0) right.setToolTip("Select footpath configuration.\nIRC 5 Clause 101.41: Safety kerb required when footpath is not present.") + # Connect to update additional inputs if open + right.currentTextChanged.connect(self.on_footpath_changed) + + # Set default value for Deck Concrete Grade + elif field[0] == KEY_DECK_CONCRETE_GRADE_BASIC: + right.setCurrentText("M25") # Set default to M25 + right.setToolTip("Select concrete grade for bridge deck.\nMinimum M25 grade required for bridge deck construction.") cur_box_form.addRow(left, right_aligned_widget(right)) @@ -409,7 +421,7 @@ def build_left_panel(self, field_list): if field[0] == KEY_SKEW_ANGLE: right.setText(str(SKEW_ANGLE_DEFAULT)) right.setPlaceholderText(f"Default: {SKEW_ANGLE_DEFAULT}°") - right.setToolTip(f"Skew angle of rolled beams or plate girders.\nIRC 5 Clause 105.3.3 recommends ≤ {SKEW_ANGLE_MAX}°") + right.setToolTip(f"Skew angle of rolled beams or plate girders.\nIRC 24 (2010) requires detailed analysis when skew angle exceeds ±15°.\nRange: {SKEW_ANGLE_MIN}° to {SKEW_ANGLE_MAX}°") elif field[0] == KEY_SPAN: right.setPlaceholderText(f"Enter value between {SPAN_MIN}-{SPAN_MAX} m") right.setToolTip(f"Total length of the steel girder bridge.\nMust be between {SPAN_MIN} m and {SPAN_MAX} m") @@ -469,4 +481,33 @@ def build_left_panel(self, field_list): """) h_scroll_area.setWidget(self.left_panel) - left_layout.addWidget(h_scroll_area) \ No newline at end of file + left_layout.addWidget(h_scroll_area) + + def show_additional_inputs(self): + """Show Additional Inputs dialog""" + # Get current footpath value + footpath_value = self.footpath_combo.currentText() if self.footpath_combo else "None" + + # Create or show existing window + if self.additional_inputs_window is None or not self.additional_inputs_window.isVisible(): + self.additional_inputs_window = QDialog(self) + self.additional_inputs_window.setWindowTitle("Additional Inputs - Manual Bridge Parameter Definition") + self.additional_inputs_window.resize(900, 700) + + layout = QVBoxLayout(self.additional_inputs_window) + layout.setContentsMargins(0, 0, 0, 0) + + # Add the additional inputs widget + self.additional_inputs_widget = AdditionalInputsWidget(footpath_value, self.additional_inputs_window) + layout.addWidget(self.additional_inputs_widget) + + self.additional_inputs_window.show() + else: + self.additional_inputs_window.raise_() + self.additional_inputs_window.activateWindow() + + def on_footpath_changed(self, footpath_value): + """Update additional inputs when footpath changes""" + if self.additional_inputs_window and self.additional_inputs_window.isVisible(): + if hasattr(self, 'additional_inputs_widget'): + self.additional_inputs_widget.update_footpath_value(footpath_value) \ No newline at end of file From a4d204556118d04c2330b915c7407b024dce4b05 Mon Sep 17 00:00:00 2001 From: Garvit S Rathore Date: Sat, 18 Oct 2025 18:55:09 +0530 Subject: [PATCH 13/59] Added validators --- .../additional_inputs.cpython-311.pyc | Bin 31370 -> 40711 bytes .../__pycache__/common.cpython-311.pyc | Bin 8361 -> 8640 bytes .../__pycache__/input_dock.cpython-311.pyc | Bin 26844 -> 27366 bytes src/osbridge/additional_inputs.py | 219 +++++++++++++++--- src/osbridge/common.py | 9 + src/osbridge/input_dock.py | 14 +- 6 files changed, 203 insertions(+), 39 deletions(-) diff --git a/src/osbridge/__pycache__/additional_inputs.cpython-311.pyc b/src/osbridge/__pycache__/additional_inputs.cpython-311.pyc index 752a0dbf1e4b8ea242a4c2073e5724a3941d6eef..3e81e54db567bcb72d94a9eeae2526ec49176556 100644 GIT binary patch delta 12005 zcmd5?32+79r1ql!&0TAGcz5l&`_t{^OpZumO^)HRaWC9Ki`e5$cYj32g4dnAE!%!Zw zt`iqXM*EEZR1%|mhB!qrNkH`s1vHt_15II)ff|?;phm_3G?g&|H8H6`(-;%bbS4dG z29pld%wzzyFlL}u#sV~Row!b(vP}@K%$@WucAmND>-ViJ`2y_Y=+lO(f`7%55k!9H z$gD=X6sa00{{P?$h%+2P$CpgVW=zjhtr63>SUeaK?&6)+2vm@p`P`MXWQKszR3m*Z z9Xe&q8BU+|vh2L~qVJmb@#$;xOki%uw0fEG2IiM9PG6Y!Ei(R{r1|Cf!1U_;D_YhC zyxDF{dN8TLq!Nf<3xq~Djc%2{2i-9`O$Bx98iC4>Ig0Ub?$*zYU(=JGt(R>DYH|pD zH~TpGG|I~vQoUQSbt$J-i*8@GrUjaSi+lu{2?FsYndpZlW|V(KkKQk^Wu)U$<#>T$ zGG0WtHD*=Vfokh)=sh|cZO|!(1zKqmE)Xj$CE4oaa1rtrWTNzfCS$QuzeX$+BMWUs4got11{iM;~vC1t2rg?Lspt4o4Q_dTYG?z^GxTH0qz^ShY5Bq)?q5 z{oHw6ZD+Djt+NO%lqDaW_h`braf4qJWWv0mR5=dJ`)g&{He$i4)E^KeyJAm}Y!JkW zLUmbNtwnCqSgv$};&RYLh1SmGFuCXoU7(NGi8Iejn0YzOJWFi=jpDiGi+zD5YAv#( zwUQdL0=-^R1Qhg>QlX3xuEfv6Sf$)Ej!SA(n`c4Qr6#d$COTgFlF_Zy16MalRGu0* zrxd}%p|Rrh@kWR*v!^FE0Ob_!14Xz3D<|IJfO7h(a;zRLk~4>t=2;|4 zBt64Cv|e*0L09!kKT%i3d%8k(wb^Q;tf>;

+?5HKisNDo}yzt7IvPx?H3ak>x)m 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 diff --git a/src/osbridge/__pycache__/common.cpython-311.pyc b/src/osbridge/__pycache__/common.cpython-311.pyc index 52031f9cc91ad921560c9227716624dd5ba60bb8..e9bc69d06960b4bfb504271001660a7290485e84 100644 GIT binary patch delta 400 zcmZ4Kc)*!&IWI340}vQZ{G7qBG?7n&aoI-oCMM19sLqs*sIHWrsP2^Bs2(7@H>EGC zFQq@KKV?$N#1_V=2`LkT88jzv-o~_vM>id01_Q$ndnn@tjKSgn6?tHvZVU#8?I&mO ze`Gv0IZ8m2apL9{0b!o{TSC5`e(`Sp{viR5As+GJo-QFC5V0UfPajV|_jnIiPj`=y zTcRL!!H#aOA(8Rku0c*vrD86wZjPZoA@S~>K`yRA@xcL(&OlwaL}B7CuFl@^{$Z{` z9*#g^X_&BckYlh%ypxw>P>?538^m6?CYaq|;i4p<|5h>-B?GCVL?H2t!zMRBr8Fni zuILMp%Lv590xX;N$(&@?Wn&PNyucuILsSApO2}Mb5WOJ@B*kt>NQ2mNiWeB9A>toc NCig0cPoA&91pw%EdCC9) delta 89 zcmX@$ywZ_xIWI340}wFve9ll(n8+u=*tJo;iHWy2rKg24sxzfKm_f5|^ChNDJd-!_ re`Gv3d9i>dW7p=h0>V6#n`C9U-vaeA0&(#Z=FKZ*Pcl!Qr>Fz~^_CpT diff --git a/src/osbridge/__pycache__/input_dock.cpython-311.pyc b/src/osbridge/__pycache__/input_dock.cpython-311.pyc index 3b077ad059d3fff3ef7c7839365b86025947154d..aa15a774369b38c35d66a69137b8b48f61496336 100644 GIT binary patch delta 945 zcmaJOsMUSP|-?n@V>DK?`nNB_*XHSm;6pMPW+2$kIEBNpYcP=6?5{^L^(rm-(E>-!^gM zn}!AxqW0^_USehFZKHXVnPTDS=w*k*WSI<_VpLW&avSc9x^1$d>Z&NyRCqbY8M17K zC6NpZLXd+2eWQbP7?Er}Q2wEE9%pBkNjW@7wjGdOM6?*G{f1FWlScFGFiK0Og8Hqq8il(JW31A&T6r^?z4@O0c~j8~<{F2dS)q?aQD)GwJQ z=Lm_Wpmb9_y8Bs2oZi>@$(OyDk2yo&(tcAy7 z^XZa(QY0iP#-nNR?pQi0$%(&1v1F8%JV^`9YAFZ;tcYjvC-^D`AG8WNLrc+YEts7O z(isCq!CMecZ3*EGA^a|~*?%i9j1+{CoTasB6%?dt2;_X7TfXpyFP!&v6?|Qln8L93 zz_ve_3tiX>^=yQC@}a&$s4wsDFZla&O+NaTw|SR>usv`9MSuIDDoIgcDOWC+6$98O z7I-Nao7zVxj(eD0go3>cbtuB}M4X>>(69L5cXgZ4y2;Yr!mc026oH@IbM8>;TAP*( z(w%HmDCra8tSHNb?7#`hjlV!dI>YE0xG(*{uOS&t*^X<&3aUdlhM!UQsv)Mucz3li a{s>oTB*)o@LW3?=*!(?KN2 zh{=j#Vr;ccHB1XQCNC5d)nrQn!(}W$Z9ohGKyAV>Hc%TQSmQkoF#({1z&-&<0*&AX z8X>|0l;xUyF+rM z8TltOCJ9e=OR8jKo_rull&i=9Xm~L*keJM#Y{IBD*(OuJ9|t1s8P+ujmv` zUX+r~TBHb4qLBJo<}Q%or^#9r2&9W{0f}4e8HvS-B_%~gcPHzoDKS2r9F%6v^p|mR zd)h0;MU#ut^Y}$stv@ip3BAo9($yIm-)xr1Y-aR!XJZUu{J?;ln!x@MBKrl8BCz`a DSjl(s diff --git a/src/osbridge/additional_inputs.py b/src/osbridge/additional_inputs.py index 01507e30..c0e223f0 100644 --- a/src/osbridge/additional_inputs.py +++ b/src/osbridge/additional_inputs.py @@ -55,9 +55,11 @@ class BridgeGeometryTab(QWidget): footpath_changed = Signal(str) # Signal when footpath status changes - def __init__(self, footpath_value="None", parent=None): + def __init__(self, footpath_value="None", carriageway_width=7.5, parent=None): super().__init__(parent) self.footpath_value = footpath_value + self.carriageway_width = carriageway_width + self.updating_fields = False # Flag to prevent circular updates self.init_ui() def style_input_field(self, field): @@ -209,21 +211,26 @@ def init_ui(self): girder_layout.setLabelAlignment(Qt.AlignLeft | Qt.AlignVCenter) self.girder_spacing = QLineEdit() - self.girder_spacing.setValidator(QDoubleValidator(0.0, 10.0, 2)) - self.girder_spacing.setPlaceholderText("Enter spacing in meters") + self.girder_spacing.setValidator(QDoubleValidator(0.01, 50.0, 3)) + self.girder_spacing.setText(str(DEFAULT_GIRDER_SPACING)) + self.girder_spacing.setPlaceholderText(f"Default: {DEFAULT_GIRDER_SPACING} m") self.style_input_field(self.girder_spacing) + self.girder_spacing.textChanged.connect(self.on_girder_spacing_changed) girder_layout.addRow("Girder Spacing (m):", self.girder_spacing) self.no_of_girders = QLineEdit() - self.no_of_girders.setEnabled(False) - self.no_of_girders.setPlaceholderText("Auto-calculated") + self.no_of_girders.setValidator(QIntValidator(2, 100)) + self.no_of_girders.setPlaceholderText("Auto-calculated integer") self.style_input_field(self.no_of_girders) + self.no_of_girders.textChanged.connect(self.on_no_of_girders_changed) girder_layout.addRow("No. of Girders:", self.no_of_girders) self.deck_overhang = QLineEdit() - self.deck_overhang.setValidator(QDoubleValidator(0.0, 5.0, 2)) - self.deck_overhang.setPlaceholderText("Enter overhang width") + self.deck_overhang.setValidator(QDoubleValidator(0.0, 10.0, 3)) + self.deck_overhang.setText(str(DEFAULT_DECK_OVERHANG)) + self.deck_overhang.setPlaceholderText(f"Default: {DEFAULT_DECK_OVERHANG} m") self.style_input_field(self.deck_overhang) + self.deck_overhang.textChanged.connect(self.on_deck_overhang_changed) girder_layout.addRow("Deck Overhang Width (m):", self.deck_overhang) inputs_layout.addWidget(girder_group) @@ -252,9 +259,11 @@ def init_ui(self): footpath_layout.setLabelAlignment(Qt.AlignLeft | Qt.AlignVCenter) self.footpath_width = QLineEdit() - self.footpath_width.setValidator(QDoubleValidator(0.0, 5.0, 2)) - self.footpath_width.setPlaceholderText("Enter width in meters") + self.footpath_width.setValidator(QDoubleValidator(MIN_FOOTPATH_WIDTH, 5.0, 3)) + self.footpath_width.setPlaceholderText(f"Min: {MIN_FOOTPATH_WIDTH} m (IRC 5 Clause 104.3.6)") self.style_input_field(self.footpath_width) + self.footpath_width.textChanged.connect(self.on_footpath_width_changed) + self.footpath_width.editingFinished.connect(self.validate_footpath_width) footpath_layout.addRow("Footpath Width (m):", self.footpath_width) self.footpath_thickness = QLineEdit() @@ -294,8 +303,10 @@ def init_ui(self): self.crash_barrier_width = QLineEdit() self.style_input_field(self.crash_barrier_width) - self.crash_barrier_width.setPlaceholderText("Barrier width") + self.crash_barrier_width.setText(str(DEFAULT_CRASH_BARRIER_WIDTH)) + self.crash_barrier_width.setPlaceholderText(f"Default: {DEFAULT_CRASH_BARRIER_WIDTH} m") self.crash_barrier_width.setValidator(QDoubleValidator(0.0, 2.0, 3)) + self.crash_barrier_width.textChanged.connect(self.recalculate_girders) crash_form.addRow("Width (m):", self.crash_barrier_width) self.crash_barrier_area = QLineEdit() @@ -316,15 +327,18 @@ def init_ui(self): railing_form.setLabelAlignment(Qt.AlignLeft | Qt.AlignVCenter) self.railing_width = QLineEdit() - self.railing_width.setValidator(QDoubleValidator(0.0, 1000.0, 0)) - self.railing_width.setPlaceholderText("Enter width in mm") + self.railing_width.setValidator(QDoubleValidator(0.0, 1.0, 3)) + self.railing_width.setText(str(DEFAULT_RAILING_WIDTH)) + self.railing_width.setPlaceholderText(f"Default: {DEFAULT_RAILING_WIDTH} m") self.style_input_field(self.railing_width) + self.railing_width.textChanged.connect(self.recalculate_girders) railing_form.addRow("Width (m):", self.railing_width) self.railing_height = QLineEdit() - self.railing_height.setValidator(QDoubleValidator(0.0, 3000.0, 0)) - self.railing_height.setPlaceholderText("Enter height in mm") + self.railing_height.setValidator(QDoubleValidator(MIN_RAILING_HEIGHT, 3.0, 3)) + self.railing_height.setPlaceholderText(f"Min: {MIN_RAILING_HEIGHT} m (IRC 5 Clauses 109.7.2.3 & 109.7.2.4)") self.style_input_field(self.railing_height) + self.railing_height.editingFinished.connect(self.validate_railing_height) railing_form.addRow("Height (m):", self.railing_height) combined_layout.addLayout(railing_form) @@ -346,8 +360,9 @@ def init_ui(self): self.safety_kerb_width = QLineEdit() self.style_input_field(self.safety_kerb_width) - self.safety_kerb_width.setPlaceholderText("Min 750mm if no footpath") - self.safety_kerb_width.setValidator(QDoubleValidator(0.0, 2.0, 3)) + self.safety_kerb_width.setValidator(QDoubleValidator(MIN_SAFETY_KERB_WIDTH, 2.0, 3)) + self.safety_kerb_width.setPlaceholderText(f"Min: {MIN_SAFETY_KERB_WIDTH} m (IRC 5 Clause 101.41)") + self.safety_kerb_width.editingFinished.connect(self.validate_safety_kerb_width) safety_kerb_form.addRow("Width (m):", self.safety_kerb_width) self.safety_kerb_thickness = QLineEdit() @@ -368,30 +383,163 @@ def init_ui(self): main_layout.addLayout(content_layout) - # Connect calculation signals - self.girder_spacing.textChanged.connect(self.calculate_no_of_girders) - self.deck_overhang.textChanged.connect(self.calculate_no_of_girders) + # Connect deck thickness to footpath thickness self.deck_thickness.textChanged.connect(self.update_footpath_thickness) + + # Initialize calculations with default values + self.recalculate_girders() def update_footpath_value(self, footpath_value): """Update visibility based on footpath selection""" self.footpath_value = footpath_value self.footpath_group.setVisible(footpath_value != "None") self.safety_kerb_container.setVisible(footpath_value == "None") + self.recalculate_girders() # Recalculate when footpath changes self.footpath_changed.emit(footpath_value) - def calculate_no_of_girders(self): - """Calculate number of girders based on spacing and overhang""" - # This would need bridge width from main inputs - # Placeholder calculation + def get_overall_bridge_width(self): + """Calculate Overall Bridge Width = Carriageway + Footpath + Crash Barrier/Railing""" + try: + overall_width = self.carriageway_width + + # Add footpath width + if self.footpath_value != "None": + footpath_width = float(self.footpath_width.text()) if self.footpath_width.text() else 0 + # Count footpaths: "Single Sided" = 1, "Both" = 2 + num_footpaths = 2 if self.footpath_value == "Both" else (1 if self.footpath_value == "Single Sided" else 0) + overall_width += footpath_width * num_footpaths + + # Add crash barrier width + crash_barrier_width = float(self.crash_barrier_width.text()) if self.crash_barrier_width.text() else DEFAULT_CRASH_BARRIER_WIDTH + # Assuming crash barriers on both edges + overall_width += crash_barrier_width * 2 + + # Add railing width (if footpath present) + if self.footpath_value != "None": + railing_width = float(self.railing_width.text()) if self.railing_width.text() else DEFAULT_RAILING_WIDTH + # Railings on both sides if footpath exists + overall_width += railing_width * 2 + + return overall_width + except: + return self.carriageway_width + + def recalculate_girders(self): + """Recalculate based on the formula: (Overall Bridge Width - Deck Overhang) / Girder Spacing = No. of Girders""" + if self.updating_fields: + return + try: - spacing = float(self.girder_spacing.text()) if self.girder_spacing.text() else 0 - overhang = float(self.deck_overhang.text()) if self.deck_overhang.text() else 0 + overall_width = self.get_overall_bridge_width() + spacing = float(self.girder_spacing.text()) if self.girder_spacing.text() else DEFAULT_GIRDER_SPACING + overhang = float(self.deck_overhang.text()) if self.deck_overhang.text() else DEFAULT_DECK_OVERHANG + + # Validate: spacing and overhang should be less than overall bridge width + if spacing >= overall_width or overhang >= overall_width: + self.no_of_girders.setText("") + return + + # Calculate: No. of Girders = (Overall Width - 2*Overhang) / Spacing + 1 if spacing > 0: - # Placeholder: assume bridge width of 10m for demo - bridge_width = 10.0 - no_girders = int((bridge_width - 2 * overhang) / spacing) + 1 - self.no_of_girders.setText(str(no_girders)) + no_girders = int(round((overall_width - 2 * overhang) / spacing)) + 1 + if no_girders >= 2: + self.updating_fields = True + self.no_of_girders.setText(str(no_girders)) + self.updating_fields = False + except: + pass + + def on_girder_spacing_changed(self): + """When user changes girder spacing, recalculate number of girders""" + if not self.updating_fields: + try: + overall_width = self.get_overall_bridge_width() + spacing_text = self.girder_spacing.text() + if spacing_text: + spacing = float(spacing_text) + if spacing >= overall_width: + QMessageBox.warning(self, "Invalid Girder Spacing", + f"Girder spacing ({spacing:.2f} m) must be less than overall bridge width ({overall_width:.2f} m).") + return + self.recalculate_girders() + except: + pass + + def on_deck_overhang_changed(self): + """When user changes deck overhang, recalculate number of girders""" + if not self.updating_fields: + try: + overall_width = self.get_overall_bridge_width() + overhang_text = self.deck_overhang.text() + if overhang_text: + overhang = float(overhang_text) + if overhang >= overall_width: + QMessageBox.warning(self, "Invalid Deck Overhang", + f"Deck overhang ({overhang:.2f} m) must be less than overall bridge width ({overall_width:.2f} m).") + return + self.recalculate_girders() + except: + pass + + def on_no_of_girders_changed(self): + """When user changes number of girders, recalculate girder spacing""" + if not self.updating_fields: + try: + no_girders_text = self.no_of_girders.text() + if no_girders_text: + no_girders = int(no_girders_text) + if no_girders < 2: + QMessageBox.warning(self, "Invalid Number of Girders", + "Number of girders must be at least 2.") + return + + overall_width = self.get_overall_bridge_width() + overhang = float(self.deck_overhang.text()) if self.deck_overhang.text() else DEFAULT_DECK_OVERHANG + + # Calculate spacing: Spacing = (Overall Width - 2*Overhang) / (No. of Girders - 1) + if no_girders > 1: + new_spacing = (overall_width - 2 * overhang) / (no_girders - 1) + self.updating_fields = True + self.girder_spacing.setText(f"{new_spacing:.3f}") + self.updating_fields = False + except: + pass + + def on_footpath_width_changed(self): + """When footpath width changes, recalculate girders""" + if not self.updating_fields: + self.recalculate_girders() + + def validate_footpath_width(self): + """Validate footpath width meets minimum IRC 5 requirements""" + try: + if self.footpath_width.text(): + width = float(self.footpath_width.text()) + if width < MIN_FOOTPATH_WIDTH: + QMessageBox.critical(self, "Footpath Width Error", + f"Footpath width must be at least {MIN_FOOTPATH_WIDTH} m as per IRC 5 Clause 104.3.6.") + except: + pass + + def validate_railing_height(self): + """Validate railing height meets minimum IRC 5 requirements""" + try: + if self.railing_height.text(): + height = float(self.railing_height.text()) + if height < MIN_RAILING_HEIGHT: + QMessageBox.critical(self, "Railing Height Error", + f"Railing height must be at least {MIN_RAILING_HEIGHT} m as per IRC 5 Clauses 109.7.2.3 and 109.7.2.4.") + except: + pass + + def validate_safety_kerb_width(self): + """Validate safety kerb width meets minimum IRC 5 requirements""" + try: + if self.safety_kerb_width.text(): + width = float(self.safety_kerb_width.text()) + if width < MIN_SAFETY_KERB_WIDTH: + QMessageBox.critical(self, "Safety Kerb Width Error", + f"Safety kerb width must be at least {MIN_SAFETY_KERB_WIDTH} m (750 mm) as per IRC 5 Clause 101.41.") except: pass @@ -403,20 +551,17 @@ def update_footpath_thickness(self): def on_crash_barrier_type_changed(self, barrier_type): """Warn if flexible/semi-rigid barrier without footpath""" if (barrier_type in ["Flexible", "Semi-Rigid"]) and (self.footpath_value == "None"): - QMessageBox.warning( - self, - "Crash Barrier Type Not Permitted", - f"{barrier_type} crash barriers are not permitted on bridges without an outer footpath per IRC 5 Clause 109.6.4.", - QMessageBox.Ok - ) + QMessageBox.critical(self, "Crash Barrier Type Not Permitted", + f"{barrier_type} crash barriers are not permitted on bridges without an outer footpath per IRC 5 Clause 109.6.4.") class AdditionalInputsWidget(QWidget): """Main widget for Additional Inputs with tabbed interface""" - def __init__(self, footpath_value="None", parent=None): + def __init__(self, footpath_value="None", carriageway_width=7.5, parent=None): super().__init__(parent) self.footpath_value = footpath_value + self.carriageway_width = carriageway_width self.init_ui() def init_ui(self): @@ -450,7 +595,7 @@ def init_ui(self): """) # Sub-Tab 1: Bridge Geometry - self.bridge_geometry_tab = BridgeGeometryTab(self.footpath_value) + self.bridge_geometry_tab = BridgeGeometryTab(self.footpath_value, self.carriageway_width) self.tabs.addTab(self.bridge_geometry_tab, "Bridge Geometry") # Sub-Tab 2: Section Properties diff --git a/src/osbridge/common.py b/src/osbridge/common.py index ce2f8de2..07f6fe18 100644 --- a/src/osbridge/common.py +++ b/src/osbridge/common.py @@ -190,6 +190,15 @@ DEFAULT_STEEL_DENSITY = 78.5 # kN/m³ DEFAULT_BEARING_LENGTH = 0.0 # mm +# Bridge Geometry Validation Constants (IRC 5) +MIN_FOOTPATH_WIDTH = 1.5 # meters (IRC 5 Clause 104.3.6) +MIN_RAILING_HEIGHT = 1.0 # meters (IRC 5 Clauses 109.7.2.3 & 109.7.2.4) +MIN_SAFETY_KERB_WIDTH = 0.75 # meters (IRC 5 Clause 101.41) +DEFAULT_GIRDER_SPACING = 2.5 # meters (preliminary design assumption) +DEFAULT_DECK_OVERHANG = 1.0 # meters (preliminary design assumption) +DEFAULT_CRASH_BARRIER_WIDTH = 0.5 # meters (typical) +DEFAULT_RAILING_WIDTH = 0.15 # meters (typical) + def connectdb(table_name, popup=None): """Mock database connection - returns sample data""" if table_name == "Material": diff --git a/src/osbridge/input_dock.py b/src/osbridge/input_dock.py index d7315e16..feadb135 100644 --- a/src/osbridge/input_dock.py +++ b/src/osbridge/input_dock.py @@ -488,6 +488,16 @@ def show_additional_inputs(self): # Get current footpath value footpath_value = self.footpath_combo.currentText() if self.footpath_combo else "None" + # Get carriageway width from basic inputs + carriageway_width = 7.5 # Default value + if self.input_widget: + carriageway_field = self.input_widget.findChild(QLineEdit, KEY_CARRIAGEWAY_WIDTH) + if carriageway_field and carriageway_field.text(): + try: + carriageway_width = float(carriageway_field.text()) + except ValueError: + carriageway_width = 7.5 + # Create or show existing window if self.additional_inputs_window is None or not self.additional_inputs_window.isVisible(): self.additional_inputs_window = QDialog(self) @@ -497,8 +507,8 @@ def show_additional_inputs(self): layout = QVBoxLayout(self.additional_inputs_window) layout.setContentsMargins(0, 0, 0, 0) - # Add the additional inputs widget - self.additional_inputs_widget = AdditionalInputsWidget(footpath_value, self.additional_inputs_window) + # Add the additional inputs widget with carriageway width + self.additional_inputs_widget = AdditionalInputsWidget(footpath_value, carriageway_width, self.additional_inputs_window) layout.addWidget(self.additional_inputs_widget) self.additional_inputs_window.show() From bebdc953d48e4a27b0da3c454e6050fbcc488432 Mon Sep 17 00:00:00 2001 From: Garvit Singh Rathore <78960005+garvit000@users.noreply.github.com> Date: Sun, 26 Oct 2025 00:02:42 +0530 Subject: [PATCH 14/59] Initialize README with project details Added project overview, technical stack, and structure. --- README.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 00000000..9f292f76 --- /dev/null +++ b/README.md @@ -0,0 +1,24 @@ +# Osdag Bridge +## Overview: +This repository contains the source code for Osdag bridge component of [Osdag](https://github.com/osdag-admin/Osdag). +## Technical Stack + +- **Framework**: PySide6 (Qt for Python) +- **Language**: Python 3.8+ +- **Architecture**: Model-View pattern with modular design + +## Project Structure + +``` +OsBridge/ +├── src/ +│ └── osbridge/ +│ ├── template_page.py # Main application window +│ ├── input_dock.py # Basic inputs panel +│ ├── additional_inputs.py # Additional inputs dialog +│ ├── backend.py # Backend logic and validation +│ ├── common.py # Shared constants and utilities +│ └── resources/ # Images and resources +├── README.md +└── requirements.txt +``` From 4801eb751b42fa6f91eb15ea1fe27fbeeb9c2f07 Mon Sep 17 00:00:00 2001 From: Garvit Singh Rathore <78960005+garvit000@users.noreply.github.com> Date: Sun, 26 Oct 2025 00:04:56 +0530 Subject: [PATCH 15/59] Enhance README with installation and usage sections Added installation instructions, prerequisites, and usage details. --- README.md | 39 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9f292f76..a2af5ba9 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # Osdag Bridge -## Overview: +## Overview This repository contains the source code for Osdag bridge component of [Osdag](https://github.com/osdag-admin/Osdag). ## Technical Stack @@ -22,3 +22,40 @@ OsBridge/ ├── README.md └── requirements.txt ``` +## Installation + +### Prerequisites +- Python 3.8 or higher +- pip package manager + +### Setup + +1. Clone the repository: +```bash +git clone https://github.com/garvit000/osdag_bridge.git +cd osdag_bridge +``` + +2. Install dependencies: +```bash +pip install -r requirements.txt +``` + +### Dependencies +``` +PySide6>=6.5.0 +``` +## Usage + +### Running the Application + +From the project root directory: +```bash +python src/osbridge/template_page.py +``` + +Or from within the osbridge directory: +```bash +cd src/osbridge +python template_page.py +``` From ce1ffd396ebb42f1910a44d063ef553771a083ab Mon Sep 17 00:00:00 2001 From: Garvit Singh Rathore <78960005+garvit000@users.noreply.github.com> Date: Sun, 26 Oct 2025 00:55:52 +0530 Subject: [PATCH 16/59] Remove installation and usage sections from README Removed installation and usage instructions from README.md. --- README.md | 37 ------------------------------------- 1 file changed, 37 deletions(-) diff --git a/README.md b/README.md index a2af5ba9..c4d69f68 100644 --- a/README.md +++ b/README.md @@ -22,40 +22,3 @@ OsBridge/ ├── README.md └── requirements.txt ``` -## Installation - -### Prerequisites -- Python 3.8 or higher -- pip package manager - -### Setup - -1. Clone the repository: -```bash -git clone https://github.com/garvit000/osdag_bridge.git -cd osdag_bridge -``` - -2. Install dependencies: -```bash -pip install -r requirements.txt -``` - -### Dependencies -``` -PySide6>=6.5.0 -``` -## Usage - -### Running the Application - -From the project root directory: -```bash -python src/osbridge/template_page.py -``` - -Or from within the osbridge directory: -```bash -cd src/osbridge -python template_page.py -``` From c6dfcd7e6cf9fe71259d585acc88e1893eac6b1e Mon Sep 17 00:00:00 2001 From: Garvit S Rathore Date: Mon, 27 Oct 2025 20:28:38 +0530 Subject: [PATCH 17/59] Update UI --- .../additional_inputs.cpython-311.pyc | Bin 40711 -> 84803 bytes .../__pycache__/backend.cpython-311.pyc | Bin 7297 -> 4927 bytes .../__pycache__/common.cpython-311.pyc | Bin 8640 -> 8720 bytes .../__pycache__/input_dock.cpython-311.pyc | Bin 27366 -> 46930 bytes src/osbridge/additional_inputs.py | 1038 ++++++++++++++++- src/osbridge/backend.py | 36 +- src/osbridge/common.py | 2 +- src/osbridge/input_dock.py | 837 +++++++++---- 8 files changed, 1661 insertions(+), 252 deletions(-) diff --git a/src/osbridge/__pycache__/additional_inputs.cpython-311.pyc b/src/osbridge/__pycache__/additional_inputs.cpython-311.pyc index 3e81e54db567bcb72d94a9eeae2526ec49176556..2d5b72dfacb601afb38903324ade5d58d4d3ed19 100644 GIT binary patch literal 84803 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 E0an*jUH||9 delta 2587 zcmZ{mdvH`&8Nly%v(IE-d2HSxo2DTwBq19iBqYG*xsG50CeQ&|mnHWmx#MPcd-iT1 zO9;sn%Ci(WZEt6s>BB$7il}&XqO}jOPRF6*gouJW+S*o&I#XT7{8P~x&vzCGOw0Y_ zx95E4dz|x~bH20hykI>1mgqcdw`U9NGthsJzB4JDb{2{A!s%smCP^F+ymtKKn&{G;3(&0J$hjbzx|k8^)}M7e0g$AhI#(JVF$n^_P*_IWT8ZnJ`t-*>n!b2b-{;OYfR{d9vJEEauA9pm+pM`aY`NEMmGidB zwBW6B-C`@qT|ltz1K0+FJ&#_#`KZ35^c4{{(c9%)VMgClaj*vb`tLgxl=*vJgDvzQ zJtv`$PW1j3y7aFG{+hL-m#sWm(n%C)L=MQ&FkE3FQSg9RGtu5T=Dc z(F;eH>Q8O_yR*X2q`sla1e0>ag@FkEGJvXqFLGLmQ_B9U(}f!9d9IH=QNxd{%@jq-9hr;p16Svwx;+=-6NdqO0_ z54}g(8*#xj$@b4@iP6+o=P~ubUpNMdmLb+OAy$Ag0r-Px-TTEK8(~4M& zXhY;9@c5GNA<7Ybh%E@L2;>I{zFj=!QS2rV1hEgXA8`V43h^9bhM|VEV{;t?-z9(M z*ls`}f^T03-7wwhTyJEx>>m7-PJad)G^Yq9+NUevq{)vq9okGCoUdBL+U&ZOp@`3z zr+^m>KI4GaUk@!NA4`6`oxW;bLeq|1?Tva^A*KvxE`M#dtpUCQ;-o-B-a><9lFXX9 z5mtjq10Uqm-CnmrvPf1huGRaP&~{H)AUP!Gw^{gexc~gy zZ`RiNpgPMeIkX`k^gx-G+XSVC{gz1!{lwT#TdPVmlM9?QSnJSko53YcTIoz}A>FLy zf|Z_bC_Hl|AAp7y3Tc0Bo>daqJC@f+9-sY96C^}2@rJg!87f@?g-9~-bjxZ;3CBIl zD4Z=`zS6Tf98zUZX3(4c(e$@G1FAASEJqe~xs*naxJ!AfCn9;el~8Pyghs~h`qL_! zp=!2gHH>Br?qCb^x(SvfvIJp9Y(mr^*t5yXyc*ga=bN1==D16#{N4PK369)(Wr9p8 zWeQ2spjOrj8&;k{^TQ0@>`Xa)9<7Y3qK4&EZd@Kxm1rawBhgqKO||h>*y_gT9!q29 z%bc`c?SodRwynXFaF8MMbQNlku7z{$X_HVhl8 zB%LQ%?I*0)EyeSe;<=LLb9Mf!mX2$djyX%m?CrHMZ+7#=@&xQ+(McX6qxtBxPL?|e#^ekS`U;AlqT!6sV*qe)Lw9l z*F^1jFRbeSUB>$tU1@$J_&s?4p5rgPhSzZz(I9G$0JP-GU|gOS?qgq;wmksrEvV!z zCvDox0XVyYUtbixCXfd7fp5`&EbU`aDG`>}lh0TeYjXUjF#F7xp%o5{o1sfg3lIN! j(R?d2y6~V9ak4)w0Z0oE|9SDTTba>?hbox^z$f~DzfPA8 diff --git a/src/osbridge/__pycache__/backend.cpython-311.pyc b/src/osbridge/__pycache__/backend.cpython-311.pyc index 5a0279641ca016a3ba03e8b5174e139876b666e3..e134a0c8e1b78fe164bba6e35ba34157be9ef242 100644 GIT binary patch delta 437 zcmZp)+^@#BoR^o20SL~n`<&s)vypE*6QkVbV@$HFlU+HY85K7#IRhmJb5kuSw^qP)dJB#(dzD~bVf8G*Ri7f5_y gW@Kc%!60%0hCZ-_GBOH%V8Bj(1dD&cAq%z+07r{&MF0Q* delta 2660 zcmbtWZActP7@pnx^6NhI&iHM|ue@8?xRq{XkP!Z{MXWbSAltjHim+uEY#Q){b-^ zA)e+nBl>BJ@(*1?S(M)mmnr-0)+~m;n5^h_3=X$D*zRHWY!BP(=yMEr_A>2`;WozY zWRvBp+vz^HTTHU_0(0iP!@ZlYE?;>s=Ui@5BR4in`9K9Lf8=yf*Kb$mzCo8gG#_56 z@4Dw&xfqIv-(ExL9qJ4XjoC#j%X#~hDdTm@p%_dYTm(B?ZB;`lbJ<6qgWWETa z56xY%*I`ubCSV5e&;aR@4Tc7u$0Fby8b#Bzoc2K*r6!U1VK$OU%tnb)nGq%FVe}P+ z(6{iU%Csh`k+rH_qvB;1rCb=+r$Pu`Rzt+PD7~OnD2?qBPi@RnV?Ki;$qa`+offz%MjJ7;&!n6Z{o6ED6U|O(jMoV5wWK@T~sIA zwlZxVuTC!7modEKe6urWf!G{5BOLm^3 zxAyt0v5d6a36!}n3t`LzMt#EO%4u~W1Dx^FO;up%1J$lq860iGwMQEo8|ttqOoY6m zAYopRIj>LPA(-=Bk-QQPkHH2>Bs=IQ>4hneAn+1iy4BbW@uQ+3NcFfUh&`Oo6Y`}t z8i&0;AK7A1I}#NA0>4AvQ}S+Gx*4h3h5GjGeMaT<)EUM7D27l{H{uI&ax%+(j_Db8 zIeXF;Ga!=7naqKk5fU7t7!;G)eOJa)rg)I}3R$MN*U5A``kBGuerJ2%X_7%aM0_T9 zx1)cU>FGS-;t(?8XtJlo84Jl76&jn)}YJ}4@mA6h(~sAyU) zYEBe2&uCYzMK@|^TW4Aq_{D+GgA2h`Tj`u@))n7B{!=Xg)O*KMZG6nPVeB7YwoN2# z6LH(b`uYP~<(JuU>!Gz6qKqfkHeuDWZ_YStj90$4Y-veYTH@)trh&Ni_4TJ&D6eY4 z70+#iO6YY_4~i@1{VRu=L}lA@F`Fo6XLPI9;+3+7`__iIwPAxBeD@9v&?~2hQbrnM z=q%kwK8oPX#777f4$@ClgmJJlNCVw2ijYp@ChgP97H7iZjHfH1-9>HiE}b8W=N^Je zc(WoA-~f@gr=8fJx0A6Ja^2N#RPrkl6*> zuMszVnGT{RtRZT`#0!{K6N57VA*?DVjhk|qWlLMa(iTrwB8Me%*5H;Dz{Ko>a2MuI z%$}IPuxzy_toC@GebsEeCQHZRUVXxpFg56xn(8)-X30zutYGx49sO#rZZ~Rv%cI(h zpebca98Ks>Q*}p$=59F!=%JMF%r)j??bs9aj|T(r?vq5P(vTX{d9wRaM; zi-3!O`lHfEtPuiZ1f0s>`Q_9XilN|`xmj1fhBhIfI15f%s&uwBvi_@% diff --git a/src/osbridge/__pycache__/common.cpython-311.pyc b/src/osbridge/__pycache__/common.cpython-311.pyc index e9bc69d06960b4bfb504271001660a7290485e84..0d37009d86072f441a796674e4093d9d83059a9c 100644 GIT binary patch delta 135 zcmX@$Ji&!`IWI340}z@?GO&l-#^RHj7#40~3R60@DWu1UXq+aVY@% CwJhWS delta 54 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 WzW8T4<58mblP^HZ#1zOw#{U4hR`#d> diff --git a/src/osbridge/additional_inputs.py b/src/osbridge/additional_inputs.py index c0e223f0..f9f9c1d7 100644 --- a/src/osbridge/additional_inputs.py +++ b/src/osbridge/additional_inputs.py @@ -69,6 +69,7 @@ def style_input_field(self, field): field.setStyleSheet(""" QComboBox { padding: 4px 8px; + padding-right: 30px; border: 1px solid #c0c0c0; border-radius: 3px; background-color: white; @@ -76,17 +77,24 @@ def style_input_field(self, field): } QComboBox::drop-down { subcontrol-origin: padding; - subcontrol-position: top right; - width: 20px; - border-left: 1px solid #c0c0c0; - background-color: #e8e8e8; + subcontrol-position: center right; + width: 18px; + height: 18px; + border: 1px solid #606060; + border-radius: 9px; + background-color: transparent; + right: 5px; + } + QComboBox::drop-down:hover { + background-color: #e0e0e0; } QComboBox::down-arrow { image: none; - border-left: 4px solid transparent; - border-right: 4px solid transparent; - border-top: 6px solid #606060; - margin-right: 5px; + width: 0; + height: 0; + border-left: 3px solid transparent; + border-right: 3px solid transparent; + border-top: 4px solid #606060; } QComboBox:focus { border: 1px solid #4a7ba7; @@ -555,6 +563,1008 @@ def on_crash_barrier_type_changed(self, barrier_type): f"{barrier_type} crash barriers are not permitted on bridges without an outer footpath per IRC 5 Clause 109.6.4.") +class SectionPropertiesTab(QWidget): + """Sub-tab for Section Properties with nested tabs for Girder, Stiffener, Cross-Bracing, and End Diaphragm""" + + def __init__(self, parent=None): + super().__init__(parent) + self.init_ui() + + def init_ui(self): + """Initialize the UI""" + main_layout = QVBoxLayout(self) + main_layout.setContentsMargins(10, 10, 10, 10) + main_layout.setSpacing(10) + + # Create nested tab widget for different section types + self.section_tabs = QTabWidget() + self.section_tabs.setStyleSheet(""" + QTabWidget::pane { + border: 1px solid #d0d0d0; + background: white; + } + QTabBar::tab { + background: #f0f0f0; + color: black; + border: 1px solid #d0d0d0; + padding: 6px 12px; + margin-right: 2px; + } + QTabBar::tab:selected { + background: #A2EBB6; + color: black; + font-weight: bold; + } + """) + + # Add nested tabs + self.girder_tab = GirderDetailsTab() + self.section_tabs.addTab(self.girder_tab, "Girder Details") + + self.stiffener_tab = StiffenerDetailsTab() + self.section_tabs.addTab(self.stiffener_tab, "Stiffener Details") + + self.cross_bracing_tab = CrossBracingDetailsTab() + self.section_tabs.addTab(self.cross_bracing_tab, "Cross-Bracing Details") + + self.end_diaphragm_tab = EndDiaphragmDetailsTab() + self.section_tabs.addTab(self.end_diaphragm_tab, "End Diaphragm Details") + + main_layout.addWidget(self.section_tabs) + + +class GirderDetailsTab(QWidget): + """Tab for Girder Details""" + + def __init__(self, parent=None): + super().__init__(parent) + self.init_ui() + + def init_ui(self): + """Initialize the UI""" + main_layout = QVBoxLayout(self) + main_layout.setContentsMargins(10, 10, 10, 10) + main_layout.setSpacing(10) + + # Scroll area + scroll = QScrollArea() + scroll.setWidgetResizable(True) + scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + + scroll_widget = QWidget() + scroll_layout = QVBoxLayout(scroll_widget) + scroll_layout.setSpacing(15) + + # Girder Type Selection + type_group = QGroupBox("Girder Type") + type_group.setStyleSheet(self.get_group_style()) + type_layout = QVBoxLayout() + + type_row = QHBoxLayout() + type_label = QLabel("Girder Type:") + type_label.setStyleSheet("font-size: 11px; min-width: 200px;") + self.girder_type_combo = QComboBox() + self.girder_type_combo.addItems(VALUES_GIRDER_TYPE) + self.style_input_field(self.girder_type_combo) + type_row.addWidget(type_label) + type_row.addWidget(self.girder_type_combo) + type_row.addStretch() + type_layout.addLayout(type_row) + + type_group.setLayout(type_layout) + scroll_layout.addWidget(type_group) + + # IS Rolled Beam Section (visible when IS Standard Rolled Beam is selected) + self.is_beam_group = QGroupBox("IS Standard Rolled Beam Section") + self.is_beam_group.setStyleSheet(self.get_group_style()) + is_beam_layout = QVBoxLayout() + + is_beam_row = QHBoxLayout() + is_beam_label = QLabel("Select IS Beam Section:") + is_beam_label.setStyleSheet("font-size: 11px; min-width: 200px;") + self.is_beam_combo = QComboBox() + # Add sample IS sections - you can expand this list + self.is_beam_combo.addItems([ + "Select Section", + "ISMB 100", "ISMB 125", "ISMB 150", "ISMB 175", "ISMB 200", + "ISMB 225", "ISMB 250", "ISMB 300", "ISMB 350", "ISMB 400", + "ISMB 450", "ISMB 500", "ISMB 550", "ISMB 600", + "ISWB 150", "ISWB 175", "ISWB 200", "ISWB 225", "ISWB 250", + "ISWB 300", "ISWB 350", "ISWB 400", "ISWB 450", "ISWB 500", "ISWB 550", "ISWB 600" + ]) + self.style_input_field(self.is_beam_combo) + is_beam_row.addWidget(is_beam_label) + is_beam_row.addWidget(self.is_beam_combo) + is_beam_row.addStretch() + is_beam_layout.addLayout(is_beam_row) + + self.is_beam_group.setLayout(is_beam_layout) + scroll_layout.addWidget(self.is_beam_group) + + # Plate Girder Section (visible when Plate Girder is selected) + self.plate_girder_group = QGroupBox("Plate Girder Details") + self.plate_girder_group.setStyleSheet(self.get_group_style()) + plate_layout = QVBoxLayout() + + # Girder Symmetry + symmetry_row = QHBoxLayout() + symmetry_label = QLabel("Girder Symmetry:") + symmetry_label.setStyleSheet("font-size: 11px; min-width: 200px;") + self.symmetry_combo = QComboBox() + self.symmetry_combo.addItems(VALUES_GIRDER_SYMMETRY) + self.style_input_field(self.symmetry_combo) + symmetry_row.addWidget(symmetry_label) + symmetry_row.addWidget(self.symmetry_combo) + symmetry_row.addStretch() + plate_layout.addLayout(symmetry_row) + + # Top Flange Width + top_width_row = QHBoxLayout() + top_width_label = QLabel("Top Flange Width (mm):") + top_width_label.setStyleSheet("font-size: 11px; min-width: 200px;") + self.top_width_field = OptimizableField("Top Flange Width") + self.style_input_field(self.top_width_field.mode_combo) + self.style_input_field(self.top_width_field.input_field) + top_width_row.addWidget(top_width_label) + top_width_row.addWidget(self.top_width_field) + top_width_row.addStretch() + plate_layout.addLayout(top_width_row) + + # Top Flange Thickness + top_thick_row = QHBoxLayout() + top_thick_label = QLabel("Top Flange Thickness (mm):") + top_thick_label.setStyleSheet("font-size: 11px; min-width: 200px;") + self.top_thick_field = OptimizableField("Top Flange Thickness") + self.style_input_field(self.top_thick_field.mode_combo) + self.style_input_field(self.top_thick_field.input_field) + top_thick_row.addWidget(top_thick_label) + top_thick_row.addWidget(self.top_thick_field) + top_thick_row.addStretch() + plate_layout.addLayout(top_thick_row) + + # Bottom Flange Width + bottom_width_row = QHBoxLayout() + bottom_width_label = QLabel("Bottom Flange Width (mm):") + bottom_width_label.setStyleSheet("font-size: 11px; min-width: 200px;") + self.bottom_width_field = OptimizableField("Bottom Flange Width") + self.style_input_field(self.bottom_width_field.mode_combo) + self.style_input_field(self.bottom_width_field.input_field) + bottom_width_row.addWidget(bottom_width_label) + bottom_width_row.addWidget(self.bottom_width_field) + bottom_width_row.addStretch() + plate_layout.addLayout(bottom_width_row) + + # Bottom Flange Thickness + bottom_thick_row = QHBoxLayout() + bottom_thick_label = QLabel("Bottom Flange Thickness (mm):") + bottom_thick_label.setStyleSheet("font-size: 11px; min-width: 200px;") + self.bottom_thick_field = OptimizableField("Bottom Flange Thickness") + self.style_input_field(self.bottom_thick_field.mode_combo) + self.style_input_field(self.bottom_thick_field.input_field) + bottom_thick_row.addWidget(bottom_thick_label) + bottom_thick_row.addWidget(self.bottom_thick_field) + bottom_thick_row.addStretch() + plate_layout.addLayout(bottom_thick_row) + + # Depth of Section + depth_row = QHBoxLayout() + depth_label = QLabel("Depth of Section (mm):") + depth_label.setStyleSheet("font-size: 11px; min-width: 200px;") + self.depth_field = OptimizableField("Depth of Section") + self.style_input_field(self.depth_field.mode_combo) + self.style_input_field(self.depth_field.input_field) + depth_row.addWidget(depth_label) + depth_row.addWidget(self.depth_field) + depth_row.addStretch() + plate_layout.addLayout(depth_row) + + # Web Thickness + web_thick_row = QHBoxLayout() + web_thick_label = QLabel("Web Thickness (mm):") + web_thick_label.setStyleSheet("font-size: 11px; min-width: 200px;") + self.web_thick_field = OptimizableField("Web Thickness") + self.style_input_field(self.web_thick_field.mode_combo) + self.style_input_field(self.web_thick_field.input_field) + web_thick_row.addWidget(web_thick_label) + web_thick_row.addWidget(self.web_thick_field) + web_thick_row.addStretch() + plate_layout.addLayout(web_thick_row) + + # Torsional Restraint + torsion_row = QHBoxLayout() + torsion_label = QLabel("Torsional Restraint:") + torsion_label.setStyleSheet("font-size: 11px; min-width: 200px;") + self.torsion_combo = QComboBox() + self.torsion_combo.addItems(VALUES_TORSIONAL_RESTRAINT) + self.style_input_field(self.torsion_combo) + torsion_row.addWidget(torsion_label) + torsion_row.addWidget(self.torsion_combo) + torsion_row.addStretch() + plate_layout.addLayout(torsion_row) + + # Warping Restraint + warp_row = QHBoxLayout() + warp_label = QLabel("Warping Restraint:") + warp_label.setStyleSheet("font-size: 11px; min-width: 200px;") + self.warp_combo = QComboBox() + self.warp_combo.addItems(VALUES_WARPING_RESTRAINT) + self.style_input_field(self.warp_combo) + warp_row.addWidget(warp_label) + warp_row.addWidget(self.warp_combo) + warp_row.addStretch() + plate_layout.addLayout(warp_row) + + # Web Type + web_type_row = QHBoxLayout() + web_type_label = QLabel("Web Type:") + web_type_label.setStyleSheet("font-size: 11px; min-width: 200px;") + self.web_type_combo = QComboBox() + self.web_type_combo.addItems(VALUES_WEB_TYPE) + self.style_input_field(self.web_type_combo) + web_type_row.addWidget(web_type_label) + web_type_row.addWidget(self.web_type_combo) + web_type_row.addStretch() + plate_layout.addLayout(web_type_row) + + self.plate_girder_group.setLayout(plate_layout) + scroll_layout.addWidget(self.plate_girder_group) + + scroll_layout.addStretch() + scroll.setWidget(scroll_widget) + main_layout.addWidget(scroll) + + # Connect signals + self.girder_type_combo.currentTextChanged.connect(self.on_girder_type_changed) + + # Initialize visibility + self.on_girder_type_changed(self.girder_type_combo.currentText()) + + def on_girder_type_changed(self, text): + """Show/hide sections based on girder type""" + if text == "IS Standard Rolled Beam": + self.is_beam_group.setVisible(True) + self.plate_girder_group.setVisible(False) + else: # Plate Girder + self.is_beam_group.setVisible(False) + self.plate_girder_group.setVisible(True) + + def style_input_field(self, field): + """Apply consistent styling to input fields""" + field.setMinimumHeight(28) + if isinstance(field, QComboBox): + field.setStyleSheet(""" + QComboBox { + padding: 4px 8px; + padding-right: 30px; + border: 1px solid #c0c0c0; + border-radius: 3px; + background-color: white; + color: #333; + } + QComboBox::drop-down { + subcontrol-origin: padding; + subcontrol-position: center right; + width: 18px; + height: 18px; + border: 1px solid #606060; + border-radius: 9px; + background-color: transparent; + right: 5px; + } + QComboBox::drop-down:hover { + background-color: #e0e0e0; + } + QComboBox::down-arrow { + image: none; + width: 0; + height: 0; + border-left: 3px solid transparent; + border-right: 3px solid transparent; + border-top: 4px solid #606060; + } + QComboBox:focus { + border: 1px solid #4a7ba7; + } + QComboBox QAbstractItemView { + background-color: white; + border: 1px solid #c0c0c0; + selection-background-color: #e8f4ff; + } + """) + elif isinstance(field, QLineEdit): + field.setStyleSheet(""" + QLineEdit { + padding: 4px 8px; + border: 1px solid #c0c0c0; + border-radius: 3px; + background-color: white; + color: #333; + } + QLineEdit:focus { + border: 1px solid #4a7ba7; + } + QLineEdit:disabled { + background-color: #f5f5f5; + color: #999; + } + """) + + def get_group_style(self): + """Return consistent group box styling""" + return """ + QGroupBox { + font-weight: bold; + font-size: 11px; + border: 1px solid #d0d0d0; + border-radius: 5px; + margin-top: 10px; + padding-top: 15px; + background-color: #fafafa; + } + QGroupBox::title { + subcontrol-origin: margin; + subcontrol-position: top left; + padding: 0 5px; + background-color: white; + } + """ + + +class StiffenerDetailsTab(QWidget): + """Tab for Stiffener Details""" + + def __init__(self, parent=None): + super().__init__(parent) + self.init_ui() + + def init_ui(self): + """Initialize the UI""" + main_layout = QVBoxLayout(self) + main_layout.setContentsMargins(10, 10, 10, 10) + main_layout.setSpacing(10) + + # Scroll area + scroll = QScrollArea() + scroll.setWidgetResizable(True) + scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + + scroll_widget = QWidget() + scroll_layout = QVBoxLayout(scroll_widget) + scroll_layout.setSpacing(15) + + # Stiffener Details Group + stiff_group = QGroupBox("Stiffener Configuration") + stiff_group.setStyleSheet(self.get_group_style()) + stiff_layout = QVBoxLayout() + + # Design Method + method_row = QHBoxLayout() + method_label = QLabel("Stiffener Design Method:") + method_label.setStyleSheet("font-size: 11px; min-width: 220px;") + self.method_combo = QComboBox() + self.method_combo.addItems(VALUES_STIFFENER_DESIGN) + self.style_input_field(self.method_combo) + method_row.addWidget(method_label) + method_row.addWidget(self.method_combo) + method_row.addStretch() + stiff_layout.addLayout(method_row) + + # Stiffener Plate Thickness + thick_row = QHBoxLayout() + thick_label = QLabel("Stiffener Plate Thickness (mm):") + thick_label.setStyleSheet("font-size: 11px; min-width: 220px;") + self.thick_combo = QComboBox() + self.thick_combo.addItems(["Optimized", "All"]) + self.style_input_field(self.thick_combo) + thick_row.addWidget(thick_label) + thick_row.addWidget(self.thick_combo) + thick_row.addStretch() + stiff_layout.addLayout(thick_row) + + # Stiffener Spacing + spacing_row = QHBoxLayout() + spacing_label = QLabel("Stiffener Spacing (mm):") + spacing_label.setStyleSheet("font-size: 11px; min-width: 220px;") + self.spacing_field = OptimizableField("Stiffener Spacing") + self.spacing_field.mode_combo.clear() + self.spacing_field.mode_combo.addItems(["Optimized", "Customized"]) + self.style_input_field(self.spacing_field.mode_combo) + self.style_input_field(self.spacing_field.input_field) + spacing_row.addWidget(spacing_label) + spacing_row.addWidget(self.spacing_field) + spacing_row.addStretch() + stiff_layout.addLayout(spacing_row) + + # Longitudinal Stiffener Requirement + long_req_row = QHBoxLayout() + long_req_label = QLabel("Longitudinal Stiffener Requirement:") + long_req_label.setStyleSheet("font-size: 11px; min-width: 220px;") + self.long_req_combo = QComboBox() + self.long_req_combo.addItems(VALUES_YES_NO) + self.style_input_field(self.long_req_combo) + long_req_row.addWidget(long_req_label) + long_req_row.addWidget(self.long_req_combo) + long_req_row.addStretch() + stiff_layout.addLayout(long_req_row) + + # Longitudinal Stiffener Thickness + long_thick_row = QHBoxLayout() + long_thick_label = QLabel("Longitudinal Stiffener Thickness (mm):") + long_thick_label.setStyleSheet("font-size: 11px; min-width: 220px;") + self.long_thick_combo = QComboBox() + self.long_thick_combo.addItems(["Optimized", "All"]) + self.long_thick_combo.setEnabled(False) # Disabled by default + self.style_input_field(self.long_thick_combo) + long_thick_row.addWidget(long_thick_label) + long_thick_row.addWidget(self.long_thick_combo) + long_thick_row.addStretch() + stiff_layout.addLayout(long_thick_row) + + stiff_group.setLayout(stiff_layout) + scroll_layout.addWidget(stiff_group) + + scroll_layout.addStretch() + scroll.setWidget(scroll_widget) + main_layout.addWidget(scroll) + + # Connect signals + self.long_req_combo.currentTextChanged.connect(self.on_long_req_changed) + + def on_long_req_changed(self, text): + """Enable/disable longitudinal stiffener thickness based on requirement""" + self.long_thick_combo.setEnabled(text == "Yes") + + def style_input_field(self, field): + """Apply consistent styling to input fields""" + field.setMinimumHeight(28) + if isinstance(field, QComboBox): + field.setStyleSheet(""" + QComboBox { + padding: 4px 8px; + padding-right: 30px; + border: 1px solid #c0c0c0; + border-radius: 3px; + background-color: white; + color: #333; + } + QComboBox::drop-down { + subcontrol-origin: padding; + subcontrol-position: center right; + width: 18px; + height: 18px; + border: 1px solid #606060; + border-radius: 9px; + background-color: transparent; + right: 5px; + } + QComboBox::drop-down:hover { + background-color: #e0e0e0; + } + QComboBox::down-arrow { + image: none; + width: 0; + height: 0; + border-left: 3px solid transparent; + border-right: 3px solid transparent; + border-top: 4px solid #606060; + } + QComboBox:focus { + border: 1px solid #4a7ba7; + } + QComboBox:disabled { + background-color: #f5f5f5; + color: #999; + } + QComboBox QAbstractItemView { + background-color: white; + border: 1px solid #c0c0c0; + selection-background-color: #e8f4ff; + } + """) + elif isinstance(field, QLineEdit): + field.setStyleSheet(""" + QLineEdit { + padding: 4px 8px; + border: 1px solid #c0c0c0; + border-radius: 3px; + background-color: white; + color: #333; + } + QLineEdit:focus { + border: 1px solid #4a7ba7; + } + QLineEdit:disabled { + background-color: #f5f5f5; + color: #999; + } + """) + + def get_group_style(self): + """Return consistent group box styling""" + return """ + QGroupBox { + font-weight: bold; + font-size: 11px; + border: 1px solid #d0d0d0; + border-radius: 5px; + margin-top: 10px; + padding-top: 15px; + background-color: #fafafa; + } + QGroupBox::title { + subcontrol-origin: margin; + subcontrol-position: top left; + padding: 0 5px; + background-color: white; + } + """ + + +class CrossBracingDetailsTab(QWidget): + """Tab for Cross-Bracing Details""" + + def __init__(self, parent=None): + super().__init__(parent) + self.init_ui() + + def init_ui(self): + """Initialize the UI""" + main_layout = QVBoxLayout(self) + main_layout.setContentsMargins(10, 10, 10, 10) + main_layout.setSpacing(10) + + # Scroll area + scroll = QScrollArea() + scroll.setWidgetResizable(True) + scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + + scroll_widget = QWidget() + scroll_layout = QVBoxLayout(scroll_widget) + scroll_layout.setSpacing(15) + + # Cross-Bracing Group + bracing_group = QGroupBox("Cross-Bracing Configuration") + bracing_group.setStyleSheet(self.get_group_style()) + bracing_layout = QVBoxLayout() + + # Type of Bracing + type_row = QHBoxLayout() + type_label = QLabel("Type of Bracing:") + type_label.setStyleSheet("font-size: 11px; min-width: 180px;") + self.type_combo = QComboBox() + self.type_combo.addItems(VALUES_CROSS_BRACING_TYPE) + self.style_input_field(self.type_combo) + type_row.addWidget(type_label) + type_row.addWidget(self.type_combo) + type_row.addStretch() + bracing_layout.addLayout(type_row) + + # Bracing Section + section_row = QHBoxLayout() + section_label = QLabel("Bracing Section:") + section_label.setStyleSheet("font-size: 11px; min-width: 180px;") + self.section_combo = QComboBox() + self.section_combo.addItems([ + "Select Section", + "ISA 50x50x6", "ISA 65x65x6", "ISA 75x75x6", "ISA 90x90x8", + "ISA 100x100x8", "ISA 110x110x10", "ISA 130x130x10", + "2-ISA 50x50x6 (LL)", "2-ISA 65x65x6 (LL)", "2-ISA 75x75x6 (LL)", + "2-ISA 50x50x6 (SL)", "2-ISA 65x65x6 (SL)", "2-ISA 75x75x6 (SL)", + "ISMC 75", "ISMC 100", "ISMC 125", "ISMC 150", + "2-ISMC 75", "2-ISMC 100", "2-ISMC 125" + ]) + self.style_input_field(self.section_combo) + section_row.addWidget(section_label) + section_row.addWidget(self.section_combo) + section_row.addStretch() + bracing_layout.addLayout(section_row) + + # Bracket Section + self.bracket_row = QHBoxLayout() + bracket_label = QLabel("Bracket Section:") + bracket_label.setStyleSheet("font-size: 11px; min-width: 180px;") + self.bracket_combo = QComboBox() + self.bracket_combo.addItems([ + "Select Section", + "ISA 50x50x6", "ISA 65x65x6", "ISA 75x75x6", "ISA 90x90x8", + "ISA 100x100x8", "ISA 110x110x10", + "2-ISA 50x50x6 (LL)", "2-ISA 65x65x6 (LL)", "2-ISA 75x75x6 (LL)", + "2-ISA 50x50x6 (SL)", "2-ISA 65x65x6 (SL)", "2-ISA 75x75x6 (SL)", + "ISMC 75", "ISMC 100", "ISMC 125", + "2-ISMC 75", "2-ISMC 100" + ]) + self.bracket_combo.setEnabled(False) # Disabled by default + self.style_input_field(self.bracket_combo) + self.bracket_row.addWidget(bracket_label) + self.bracket_row.addWidget(self.bracket_combo) + self.bracket_row.addStretch() + bracing_layout.addLayout(self.bracket_row) + + # Spacing + spacing_row = QHBoxLayout() + spacing_label = QLabel("Spacing (mm):") + spacing_label.setStyleSheet("font-size: 11px; min-width: 180px;") + self.spacing_input = QLineEdit() + self.spacing_input.setPlaceholderText("Enter spacing in mm") + self.spacing_input.setValidator(QDoubleValidator(0, 100000, 2)) + self.style_input_field(self.spacing_input) + spacing_row.addWidget(spacing_label) + spacing_row.addWidget(self.spacing_input) + spacing_row.addStretch() + bracing_layout.addLayout(spacing_row) + + bracing_group.setLayout(bracing_layout) + scroll_layout.addWidget(bracing_group) + + scroll_layout.addStretch() + scroll.setWidget(scroll_widget) + main_layout.addWidget(scroll) + + # Connect signals + self.type_combo.currentTextChanged.connect(self.on_bracing_type_changed) + + def on_bracing_type_changed(self, text): + """Enable/disable bracket section based on bracing type""" + has_bracket = "bracket" in text.lower() + self.bracket_combo.setEnabled(has_bracket) + + def style_input_field(self, field): + """Apply consistent styling to input fields""" + field.setMinimumHeight(28) + if isinstance(field, QComboBox): + field.setStyleSheet(""" + QComboBox { + padding: 4px 8px; + padding-right: 30px; + border: 1px solid #c0c0c0; + border-radius: 3px; + background-color: white; + color: #333; + } + QComboBox::drop-down { + subcontrol-origin: padding; + subcontrol-position: center right; + width: 18px; + height: 18px; + border: 1px solid #606060; + border-radius: 9px; + background-color: transparent; + right: 5px; + } + QComboBox::drop-down:hover { + background-color: #e0e0e0; + } + QComboBox::down-arrow { + image: none; + width: 0; + height: 0; + border-left: 3px solid transparent; + border-right: 3px solid transparent; + border-top: 4px solid #606060; + } + QComboBox:focus { + border: 1px solid #4a7ba7; + } + QComboBox:disabled { + background-color: #f5f5f5; + color: #999; + } + QComboBox QAbstractItemView { + background-color: white; + border: 1px solid #c0c0c0; + selection-background-color: #e8f4ff; + } + """) + elif isinstance(field, QLineEdit): + field.setStyleSheet(""" + QLineEdit { + padding: 4px 8px; + border: 1px solid #c0c0c0; + border-radius: 3px; + background-color: white; + color: #333; + } + QLineEdit:focus { + border: 1px solid #4a7ba7; + } + QLineEdit:disabled { + background-color: #f5f5f5; + color: #999; + } + """) + + def get_group_style(self): + """Return consistent group box styling""" + return """ + QGroupBox { + font-weight: bold; + font-size: 11px; + border: 1px solid #d0d0d0; + border-radius: 5px; + margin-top: 10px; + padding-top: 15px; + background-color: #fafafa; + } + QGroupBox::title { + subcontrol-origin: margin; + subcontrol-position: top left; + padding: 0 5px; + background-color: white; + } + """ + + +class EndDiaphragmDetailsTab(QWidget): + """Tab for End Diaphragm Details""" + + def __init__(self, parent=None): + super().__init__(parent) + self.init_ui() + + def init_ui(self): + """Initialize the UI""" + main_layout = QVBoxLayout(self) + main_layout.setContentsMargins(10, 10, 10, 10) + main_layout.setSpacing(10) + + # Scroll area + scroll = QScrollArea() + scroll.setWidgetResizable(True) + scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + + scroll_widget = QWidget() + scroll_layout = QVBoxLayout(scroll_widget) + scroll_layout.setSpacing(15) + + # End Diaphragm Group + diaphragm_group = QGroupBox("End Diaphragm Configuration") + diaphragm_group.setStyleSheet(self.get_group_style()) + diaphragm_layout = QVBoxLayout() + + # Type of Section + type_row = QHBoxLayout() + type_label = QLabel("Type of Section:") + type_label.setStyleSheet("font-size: 11px; min-width: 200px;") + self.type_combo = QComboBox() + self.type_combo.addItems(VALUES_END_DIAPHRAGM_TYPE) + self.style_input_field(self.type_combo) + type_row.addWidget(type_label) + type_row.addWidget(self.type_combo) + type_row.addStretch() + diaphragm_layout.addLayout(type_row) + + diaphragm_group.setLayout(diaphragm_layout) + scroll_layout.addWidget(diaphragm_group) + + # IS Rolled Beam Section + self.is_beam_group = QGroupBox("IS Standard Rolled Beam Section") + self.is_beam_group.setStyleSheet(self.get_group_style()) + is_beam_layout = QVBoxLayout() + + is_beam_row = QHBoxLayout() + is_beam_label = QLabel("Select IS Beam Section:") + is_beam_label.setStyleSheet("font-size: 11px; min-width: 200px;") + self.is_beam_combo = QComboBox() + self.is_beam_combo.addItems([ + "Select Section", + "ISMB 100", "ISMB 125", "ISMB 150", "ISMB 175", "ISMB 200", + "ISMB 225", "ISMB 250", "ISMB 300", "ISMB 350", "ISMB 400", + "ISWB 150", "ISWB 175", "ISWB 200", "ISWB 225", "ISWB 250", + "ISWB 300", "ISWB 350", "ISWB 400" + ]) + self.style_input_field(self.is_beam_combo) + is_beam_row.addWidget(is_beam_label) + is_beam_row.addWidget(self.is_beam_combo) + is_beam_row.addStretch() + is_beam_layout.addLayout(is_beam_row) + + self.is_beam_group.setLayout(is_beam_layout) + scroll_layout.addWidget(self.is_beam_group) + + # Plate Girder Section + self.plate_girder_group = QGroupBox("Plate Girder Details") + self.plate_girder_group.setStyleSheet(self.get_group_style()) + plate_layout = QVBoxLayout() + + # Top Flange Width + top_width_row = QHBoxLayout() + top_width_label = QLabel("Top Flange Width (mm):") + top_width_label.setStyleSheet("font-size: 11px; min-width: 200px;") + self.top_width_field = OptimizableField("Top Flange Width") + self.style_input_field(self.top_width_field.mode_combo) + self.style_input_field(self.top_width_field.input_field) + top_width_row.addWidget(top_width_label) + top_width_row.addWidget(self.top_width_field) + top_width_row.addStretch() + plate_layout.addLayout(top_width_row) + + # Top Flange Thickness + top_thick_row = QHBoxLayout() + top_thick_label = QLabel("Top Flange Thickness (mm):") + top_thick_label.setStyleSheet("font-size: 11px; min-width: 200px;") + self.top_thick_field = OptimizableField("Top Flange Thickness") + self.style_input_field(self.top_thick_field.mode_combo) + self.style_input_field(self.top_thick_field.input_field) + top_thick_row.addWidget(top_thick_label) + top_thick_row.addWidget(self.top_thick_field) + top_thick_row.addStretch() + plate_layout.addLayout(top_thick_row) + + # Bottom Flange Width + bottom_width_row = QHBoxLayout() + bottom_width_label = QLabel("Bottom Flange Width (mm):") + bottom_width_label.setStyleSheet("font-size: 11px; min-width: 200px;") + self.bottom_width_field = OptimizableField("Bottom Flange Width") + self.style_input_field(self.bottom_width_field.mode_combo) + self.style_input_field(self.bottom_width_field.input_field) + bottom_width_row.addWidget(bottom_width_label) + bottom_width_row.addWidget(self.bottom_width_field) + bottom_width_row.addStretch() + plate_layout.addLayout(bottom_width_row) + + # Bottom Flange Thickness + bottom_thick_row = QHBoxLayout() + bottom_thick_label = QLabel("Bottom Flange Thickness (mm):") + bottom_thick_label.setStyleSheet("font-size: 11px; min-width: 200px;") + self.bottom_thick_field = OptimizableField("Bottom Flange Thickness") + self.style_input_field(self.bottom_thick_field.mode_combo) + self.style_input_field(self.bottom_thick_field.input_field) + bottom_thick_row.addWidget(bottom_thick_label) + bottom_thick_row.addWidget(self.bottom_thick_field) + bottom_thick_row.addStretch() + plate_layout.addLayout(bottom_thick_row) + + # Depth of Section + depth_row = QHBoxLayout() + depth_label = QLabel("Depth of Section (mm):") + depth_label.setStyleSheet("font-size: 11px; min-width: 200px;") + self.depth_field = OptimizableField("Depth of Section") + self.style_input_field(self.depth_field.mode_combo) + self.style_input_field(self.depth_field.input_field) + depth_row.addWidget(depth_label) + depth_row.addWidget(self.depth_field) + depth_row.addStretch() + plate_layout.addLayout(depth_row) + + # Web Thickness + web_thick_row = QHBoxLayout() + web_thick_label = QLabel("Web Thickness (mm):") + web_thick_label.setStyleSheet("font-size: 11px; min-width: 200px;") + self.web_thick_field = OptimizableField("Web Thickness") + self.style_input_field(self.web_thick_field.mode_combo) + self.style_input_field(self.web_thick_field.input_field) + web_thick_row.addWidget(web_thick_label) + web_thick_row.addWidget(self.web_thick_field) + web_thick_row.addStretch() + plate_layout.addLayout(web_thick_row) + + self.plate_girder_group.setLayout(plate_layout) + scroll_layout.addWidget(self.plate_girder_group) + + # Spacing + spacing_group = QGroupBox("Spacing") + spacing_group.setStyleSheet(self.get_group_style()) + spacing_layout = QVBoxLayout() + + spacing_row = QHBoxLayout() + spacing_label = QLabel("Spacing (mm):") + spacing_label.setStyleSheet("font-size: 11px; min-width: 200px;") + self.spacing_input = QLineEdit() + self.spacing_input.setPlaceholderText("Enter spacing in mm") + self.spacing_input.setValidator(QDoubleValidator(0, 100000, 2)) + self.style_input_field(self.spacing_input) + spacing_row.addWidget(spacing_label) + spacing_row.addWidget(self.spacing_input) + spacing_row.addStretch() + spacing_layout.addLayout(spacing_row) + + spacing_group.setLayout(spacing_layout) + scroll_layout.addWidget(spacing_group) + + scroll_layout.addStretch() + scroll.setWidget(scroll_widget) + main_layout.addWidget(scroll) + + # Connect signals + self.type_combo.currentTextChanged.connect(self.on_type_changed) + + # Initialize visibility + self.on_type_changed(self.type_combo.currentText()) + + def on_type_changed(self, text): + """Show/hide sections based on diaphragm type""" + if text == "Same as cross-bracing": + self.is_beam_group.setVisible(False) + self.plate_girder_group.setVisible(False) + elif text == "Rolled Beam Section": + self.is_beam_group.setVisible(True) + self.plate_girder_group.setVisible(False) + else: # Plate Girder Section + self.is_beam_group.setVisible(False) + self.plate_girder_group.setVisible(True) + + def style_input_field(self, field): + """Apply consistent styling to input fields""" + field.setMinimumHeight(28) + if isinstance(field, QComboBox): + field.setStyleSheet(""" + QComboBox { + padding: 4px 8px; + padding-right: 30px; + border: 1px solid #c0c0c0; + border-radius: 3px; + background-color: white; + color: #333; + } + QComboBox::drop-down { + subcontrol-origin: padding; + subcontrol-position: center right; + width: 18px; + height: 18px; + border: 1px solid #606060; + border-radius: 9px; + background-color: transparent; + right: 5px; + } + QComboBox::drop-down:hover { + background-color: #e0e0e0; + } + QComboBox::down-arrow { + image: none; + width: 0; + height: 0; + border-left: 3px solid transparent; + border-right: 3px solid transparent; + border-top: 4px solid #606060; + } + QComboBox:focus { + border: 1px solid #4a7ba7; + } + QComboBox QAbstractItemView { + background-color: white; + border: 1px solid #c0c0c0; + selection-background-color: #e8f4ff; + } + """) + elif isinstance(field, QLineEdit): + field.setStyleSheet(""" + QLineEdit { + padding: 4px 8px; + border: 1px solid #c0c0c0; + border-radius: 3px; + background-color: white; + color: #333; + } + QLineEdit:focus { + border: 1px solid #4a7ba7; + } + QLineEdit:disabled { + background-color: #f5f5f5; + color: #999; + } + """) + + def get_group_style(self): + """Return consistent group box styling""" + return """ + QGroupBox { + font-weight: bold; + font-size: 11px; + border: 1px solid #d0d0d0; + border-radius: 5px; + margin-top: 10px; + padding-top: 15px; + background-color: #fafafa; + } + QGroupBox::title { + subcontrol-origin: margin; + subcontrol-position: top left; + padding: 0 5px; + background-color: white; + } + """ + + class AdditionalInputsWidget(QWidget): """Main widget for Additional Inputs with tabbed interface""" @@ -599,16 +1609,8 @@ def init_ui(self): self.tabs.addTab(self.bridge_geometry_tab, "Bridge Geometry") # Sub-Tab 2: Section Properties - section_props_tab = self.create_placeholder_tab( - "Section Properties", - "This tab will contain:\n\n" + - "• Girder Details (IS Standard Rolled Beam / Plate Girder)\n" + - "• Stiffener Details\n" + - "• Cross-Bracing Details\n" + - "• End Diaphragm Details\n\n" + - "Implementation in progress..." - ) - self.tabs.addTab(section_props_tab, "Section Properties") + self.section_properties_tab = SectionPropertiesTab() + self.tabs.addTab(self.section_properties_tab, "Section Properties") # Sub-Tab 3: Dead Load Inputs dead_load_tab = self.create_placeholder_tab( diff --git a/src/osbridge/backend.py b/src/osbridge/backend.py index 9bf499d5..731d7856 100644 --- a/src/osbridge/backend.py +++ b/src/osbridge/backend.py @@ -61,9 +61,6 @@ def input_values(self): t14 = (KEY_DECK, KEY_DISP_DECK, TYPE_COMBOBOX, connectdb("Material"), True, 'No Validator') options_list.append(t14) - t15 = (KEY_DECK_CONCRETE_GRADE_BASIC, KEY_DISP_DECK_CONCRETE_GRADE, TYPE_COMBOBOX, VALUES_DECK_CONCRETE_GRADE, True, 'No Validator') - options_list.append(t15) - return options_list def customized_input(self): @@ -83,38 +80,7 @@ def output_values(self, flag): return [] def func_for_validation(self, design_inputs): - """Validate user inputs according to IRC 5 specifications""" - errors = [] - - # Validate Span - if KEY_SPAN in design_inputs: - try: - span = float(design_inputs[KEY_SPAN]) - if span < SPAN_MIN or span > SPAN_MAX: - errors.append(f"Span must be between {SPAN_MIN} m and {SPAN_MAX} m. Current value: {span} m") - except (ValueError, TypeError): - errors.append("Span must be a valid number") - - # Validate Carriageway Width - if KEY_CARRIAGEWAY_WIDTH in design_inputs: - try: - width = float(design_inputs[KEY_CARRIAGEWAY_WIDTH]) - if width < CARRIAGEWAY_WIDTH_MIN: - errors.append(f"Carriageway Width must be at least {CARRIAGEWAY_WIDTH_MIN} m as per IRC 5 Clause 104.3.1. Current value: {width} m") - except (ValueError, TypeError): - errors.append("Carriageway Width must be a valid number") - - # Validate Skew Angle - if KEY_SKEW_ANGLE in design_inputs: - try: - angle = float(design_inputs[KEY_SKEW_ANGLE]) - if angle < SKEW_ANGLE_MIN or angle > SKEW_ANGLE_MAX: - errors.append(f"Skew Angle must be between {SKEW_ANGLE_MIN}° and {SKEW_ANGLE_MAX}°. IRC 24 (2010) requires detailed analysis when skew angle exceeds ±15 degrees. No calculations will be performed. Current value: {angle}°") - except (ValueError, TypeError): - errors.append("Skew Angle must be a valid number") - - if errors: - return "\n".join(errors) + """Mock validation - always passes for testing""" return None def get_3d_components(self): diff --git a/src/osbridge/common.py b/src/osbridge/common.py index 07f6fe18..c220836a 100644 --- a/src/osbridge/common.py +++ b/src/osbridge/common.py @@ -177,7 +177,7 @@ VALUES_WARPING_RESTRAINT = ["Both Flange Restraint", "No Restraint"] VALUES_WEB_TYPE = ["Thin Web", "Thick Web"] VALUES_STIFFENER_DESIGN = ["Simple Post", "Tension Field"] -VALUES_CROSS_BRACING_TYPE = ["K-bracing", "K-bracing with top bracket", "X-bracing"] +VALUES_CROSS_BRACING_TYPE = ["K-bracing", "K-bracing with top bracket", "X-bracing", "X-bracing with bottom bracket", "X-bracing with top and bottom brackets"] VALUES_END_DIAPHRAGM_TYPE = ["Same as cross-bracing", "Rolled Beam Section", "Plate Girder Section"] VALUES_WEARING_COAT_MATERIAL = ["Concrete", "Bituminous", "Other"] VALUES_CUSTOM_AXLE_TYPE = ["Single", "Bogie"] diff --git a/src/osbridge/input_dock.py b/src/osbridge/input_dock.py index feadb135..9c03fc50 100644 --- a/src/osbridge/input_dock.py +++ b/src/osbridge/input_dock.py @@ -1,7 +1,7 @@ import sys from PySide6.QtWidgets import ( QApplication, QWidget, QHBoxLayout, QVBoxLayout, QPushButton, - QComboBox, QScrollArea, QLabel, QFormLayout, QLineEdit, QGroupBox, QSizePolicy, QMessageBox, QInputDialog, QDialog + QComboBox, QScrollArea, QLabel, QFormLayout, QLineEdit, QGroupBox, QSizePolicy, QMessageBox, QInputDialog, QDialog, QCheckBox, QFrame ) from PySide6.QtCore import Qt, QRegularExpression from PySide6.QtGui import QPixmap, QDoubleValidator, QRegularExpressionValidator @@ -15,67 +15,59 @@ def wheelEvent(self, event): event.ignore() -def right_aligned_widget(widget): - """Right-align widget horizontally within its container""" - container = QWidget() - layout = QHBoxLayout(container) - layout.setContentsMargins(0, 0, 0, 0) - layout.addStretch() - layout.addWidget(widget) - layout.setAlignment(widget, Qt.AlignRight | Qt.AlignVCenter) - return container - - -def left_aligned_widget(widget): - container = QWidget() - layout = QHBoxLayout(container) - layout.setContentsMargins(0, 0, 0, 0) - layout.addWidget(widget) - layout.addStretch() - layout.setAlignment(widget, Qt.AlignVCenter) - return container - - def apply_field_style(widget): - widget.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed) - widget.setMaximumWidth(180) - widget.setMinimumHeight(24) + widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) + widget.setMinimumHeight(28) if isinstance(widget, QComboBox): style = """ QComboBox { - padding: 3px 5px; - border: 1px solid #c0c0c0; + padding: 4px 8px; + padding-right: 30px; + border: 1px solid #b0b0b0; border-radius: 3px; background-color: white; color: black; - min-height: 20px; + min-height: 24px; } QComboBox::drop-down { subcontrol-origin: padding; - subcontrol-position: top right; - width: 20px; - border-left: 1px solid #c0c0c0; - background-color: #e8e8e8; + subcontrol-position: center right; + width: 18px; + height: 18px; + border: 1px solid #606060; + border-radius: 9px; + background-color: transparent; + right: 5px; + } + QComboBox::drop-down:hover { + background-color: #e0e0e0; } QComboBox::down-arrow { image: none; - border-left: 4px solid transparent; - border-right: 4px solid transparent; - border-top: 6px solid #606060; - margin-right: 5px; + width: 0; + height: 0; + border-left: 3px solid transparent; + border-right: 3px solid transparent; + border-top: 4px solid #606060; + } + QComboBox:hover { + border: 1px solid #909090; + } + QComboBox:focus { + border: 1px solid #4a7ba7; } QComboBox QAbstractItemView { background-color: white; - border: 1px solid #c0c0c0; + border: 1px solid #b0b0b0; outline: none; selection-background-color: #e8f4ff; } QComboBox QAbstractItemView::item { color: black; background-color: white; - padding: 4px; - min-height: 20px; + padding: 5px; + min-height: 24px; } QComboBox QAbstractItemView::item:hover { background-color: #e8f4ff; @@ -86,16 +78,23 @@ def apply_field_style(widget): elif isinstance(widget, QLineEdit): widget.setStyleSheet(""" QLineEdit { - padding: 3px 5px; - border: 1px solid #c0c0c0; + padding: 4px 8px; + border: 1px solid #b0b0b0; border-radius: 3px; background-color: white; color: black; - min-height: 20px; + min-height: 24px; + } + QLineEdit:hover { + border: 1px solid #909090; } QLineEdit:focus { border: 1px solid #4a7ba7; } + QLineEdit:disabled { + background-color: #f5f5f5; + color: #999; + } """) @@ -119,17 +118,60 @@ def style_main_buttons(): """ +def create_group_box(title): + """Create a styled group box""" + group_box = QGroupBox(title) + group_box.setStyleSheet(""" + QGroupBox { + font-weight: bold; + font-size: 11px; + color: #333; + border: 1px solid #d0d0d0; + border-radius: 5px; + margin-top: 10px; + padding-top: 10px; + background-color: #fafafa; + } + QGroupBox::title { + subcontrol-origin: margin; + subcontrol-position: top left; + padding: 0 5px; + background-color: white; + } + """) + return group_box + + +def create_form_row(label_text, widget, tooltip=None): + """Create a horizontal layout with label and widget side by side""" + row = QHBoxLayout() + row.setSpacing(10) + + label = QLabel(label_text) + label.setStyleSheet("font-size: 10px; color: #555; font-weight: normal;") + label.setMinimumWidth(140) + label.setMaximumWidth(140) + + if tooltip: + widget.setToolTip(tooltip) + + row.addWidget(label) + row.addWidget(widget, 1) + + return row + + class InputDock(QWidget): def __init__(self, backend, parent): super().__init__() self.parent = parent self.backend = backend self.input_widget = None - self.structure_type_combo = None # Store reference to structure type combo box - self.project_location_combo = None # Store reference to project location combo box - self.custom_location_input = None # Store reference to custom location text field - self.footpath_combo = None # Store reference to footpath combo box - self.additional_inputs_window = None # Store reference to additional inputs window + self.structure_type_combo = None + self.project_location_combo = None + self.custom_location_input = None + self.footpath_combo = None + self.additional_inputs_window = None self.setStyleSheet("background: transparent;") self.main_layout = QHBoxLayout(self) @@ -184,19 +226,15 @@ def get_validator(self, validator): def on_structure_type_changed(self, text): """Handle structure type combo box changes""" if text == "Other": - # Show warning message when "Other" is selected - QMessageBox.warning( - self, - "Structure Type Not Supported", - "The selected structure type is not included currently.\n\n" - "This application currently only covers Highway Bridge design.", - QMessageBox.Ok - ) + if hasattr(self, 'structure_note'): + self.structure_note.setVisible(True) + else: + if hasattr(self, 'structure_note'): + self.structure_note.setVisible(False) def on_project_location_changed(self, text): """Handle project location combo box changes""" if text == "Custom": - # Show input dialog for custom location custom_location, ok = QInputDialog.getText( self, "Custom Location", @@ -205,7 +243,6 @@ def on_project_location_changed(self, text): "" ) if ok and custom_location.strip(): - # Store the custom location self.custom_location_input = custom_location.strip() QMessageBox.information( self, @@ -216,17 +253,357 @@ def on_project_location_changed(self, text): ) self.project_location_combo.addItem(custom_location.strip()) elif ok: - # User clicked OK but didn't enter anything - revert to previous selection QMessageBox.warning( self, "No Location Entered", "Please enter a valid city name or select from the dropdown.", QMessageBox.Ok ) - # Reset to first item if no custom value entered if self.project_location_combo: self.project_location_combo.setCurrentIndex(0) + def show_project_location_dialog(self): + """Show Project Location selection dialog""" + state_districts = { + "Select State": ["Select District"], + "Delhi": ["Central Delhi", "East Delhi", "New Delhi", "North Delhi", "North East Delhi", + "North West Delhi", "South Delhi", "South East Delhi", "South West Delhi", "West Delhi"], + "Maharashtra": ["Mumbai", "Pune", "Nagpur", "Thane", "Nashik", "Aurangabad", "Solapur", + "Amravati", "Kolhapur", "Raigad", "Satara", "Sangli"], + "Karnataka": ["Bangalore", "Mysore", "Hubli", "Belgaum", "Mangalore", "Gulbarga", + "Bellary", "Bijapur", "Shimoga", "Tumkur", "Davangere"], + "Tamil Nadu": ["Chennai", "Coimbatore", "Madurai", "Tiruchirappalli", "Salem", "Tirunelveli", + "Tiruppur", "Erode", "Vellore", "Thoothukudi", "Dindigul"], + "West Bengal": ["Kolkata", "Howrah", "Darjeeling", "Siliguri", "Asansol", "Durgapur", + "Bardhaman", "Malda", "Jalpaiguri", "Murshidabad", "Nadia"] + } + + dialog = QDialog(self) + dialog.setWindowTitle("Project Location") + dialog.setMinimumWidth(850) + dialog.setMinimumHeight(650) + + # Set white background for the entire dialog + dialog.setStyleSheet(""" + QDialog { + background-color: white; + } + QCheckBox { + color: black; + } + QLabel { + color: black; + } + """) + + main_layout = QVBoxLayout(dialog) + main_layout.setContentsMargins(20, 20, 20, 20) + main_layout.setSpacing(15) + + # === Enter Coordinates Row === + coords_row = QHBoxLayout() + coords_row.setSpacing(15) + + self.coords_checkbox = QCheckBox("Enter Coordinates") + self.coords_checkbox.setStyleSheet(""" + QCheckBox { + font-size: 12px; + font-weight: normal; + color: black; + spacing: 8px; + } + QCheckBox::indicator { + width: 18px; + height: 18px; + border: 2px solid #b0b0b0; + border-radius: 3px; + background-color: white; + } + QCheckBox::indicator:checked { + background-color: #90AF13; + border-color: #90AF13; + } + QCheckBox::indicator:hover { + border-color: #7a9a12; + } + """) + coords_row.addWidget(self.coords_checkbox) + + coords_row.addStretch() + + lat_label = QLabel("Latitude (°):") + lat_label.setStyleSheet("font-size: 11px;") + coords_row.addWidget(lat_label) + + self.latitude_input = QLineEdit() + self.latitude_input.setMaximumWidth(120) + self.latitude_input.setEnabled(False) + apply_field_style(self.latitude_input) + coords_row.addWidget(self.latitude_input) + + lng_label = QLabel("Longitude (°):") + lng_label.setStyleSheet("font-size: 11px;") + coords_row.addWidget(lng_label) + + self.longitude_input = QLineEdit() + self.longitude_input.setMaximumWidth(120) + self.longitude_input.setEnabled(False) + apply_field_style(self.longitude_input) + coords_row.addWidget(self.longitude_input) + + main_layout.addLayout(coords_row) + + # Separator line + line1 = QFrame() + line1.setFrameShape(QFrame.HLine) + line1.setFrameShadow(QFrame.Sunken) + line1.setStyleSheet("background-color: #d0d0d0;") + main_layout.addWidget(line1) + + # === Enter Location Name Row === + location_row = QHBoxLayout() + location_row.setSpacing(15) + + self.location_checkbox = QCheckBox("Enter Location Name") + self.location_checkbox.setStyleSheet(""" + QCheckBox { + font-size: 12px; + font-weight: normal; + color: black; + spacing: 8px; + } + QCheckBox::indicator { + width: 18px; + height: 18px; + border: 2px solid #b0b0b0; + border-radius: 3px; + background-color: white; + } + QCheckBox::indicator:checked { + background-color: #90AF13; + border-color: #90AF13; + } + QCheckBox::indicator:hover { + border-color: #7a9a12; + } + """) + location_row.addWidget(self.location_checkbox) + + location_row.addStretch() + + state_label = QLabel("State") + state_label.setStyleSheet("font-size: 11px;") + location_row.addWidget(state_label) + + self.state_combo = NoScrollComboBox() + self.state_combo.setMaximumWidth(150) + self.state_combo.setEnabled(False) + self.state_combo.addItems(list(state_districts.keys())) + apply_field_style(self.state_combo) + location_row.addWidget(self.state_combo) + + district_label = QLabel("District") + district_label.setStyleSheet("font-size: 11px;") + location_row.addWidget(district_label) + + self.district_combo = NoScrollComboBox() + self.district_combo.setMaximumWidth(150) + self.district_combo.setEnabled(False) + self.district_combo.addItems(["Select District"]) + apply_field_style(self.district_combo) + location_row.addWidget(self.district_combo) + + main_layout.addLayout(location_row) + + # Separator line + line2 = QFrame() + line2.setFrameShape(QFrame.HLine) + line2.setFrameShadow(QFrame.Sunken) + line2.setStyleSheet("background-color: #d0d0d0;") + main_layout.addWidget(line2) + + # === Select on Map Section === + map_section = QVBoxLayout() + map_section.setSpacing(8) + + self.map_checkbox = QCheckBox("Select on Map") + self.map_checkbox.setStyleSheet(""" + QCheckBox { + font-size: 12px; + font-weight: normal; + color: black; + spacing: 8px; + } + QCheckBox::indicator { + width: 18px; + height: 18px; + border: 2px solid #b0b0b0; + border-radius: 3px; + background-color: white; + } + QCheckBox::indicator:checked { + background-color: #90AF13; + border-color: #90AF13; + } + QCheckBox::indicator:hover { + border-color: #7a9a12; + } + """) + map_section.addWidget(self.map_checkbox) + + # Map placeholder + self.map_placeholder = QLabel() + self.map_placeholder.setStyleSheet(""" + QLabel { + border: 1px solid #e0e0e0; + background-color: white; + padding: 20px; + color: #999999; + } + """) + self.map_placeholder.setAlignment(Qt.AlignCenter) + self.map_placeholder.setMinimumHeight(200) + self.map_placeholder.setText("Map Placeholder\n(Will be added later)") + self.map_placeholder.setEnabled(False) # Disabled by default + map_section.addWidget(self.map_placeholder) + + main_layout.addLayout(map_section) + + # Separator line + line3 = QFrame() + line3.setFrameShape(QFrame.HLine) + line3.setFrameShadow(QFrame.Sunken) + line3.setStyleSheet("background-color: #d0d0d0;") + main_layout.addWidget(line3) + + # === IRC 6 (2017) Values Section === + results_section = QVBoxLayout() + results_section.setSpacing(8) + + results_title = QLabel("IRC 6 (2017) Values:") + results_title.setStyleSheet("font-size: 12px; font-weight: bold; color: #4CAF50;") + results_section.addWidget(results_title) + + self.wind_speed_label = QLabel("Basic Wind Speed (m/sec)") + self.wind_speed_label.setStyleSheet("font-size: 11px; color: #4CAF50;") + results_section.addWidget(self.wind_speed_label) + + self.seismic_zone_label = QLabel("Seismic Zone and Zone Factor") + self.seismic_zone_label.setStyleSheet("font-size: 11px; color: #4CAF50;") + results_section.addWidget(self.seismic_zone_label) + + self.temp_label = QLabel("Shade Air Temperature (°C)") + self.temp_label.setStyleSheet("font-size: 11px; color: #4CAF50;") + results_section.addWidget(self.temp_label) + + main_layout.addLayout(results_section) + + # Separator line + line4 = QFrame() + line4.setFrameShape(QFrame.HLine) + line4.setFrameShadow(QFrame.Sunken) + line4.setStyleSheet("background-color: #d0d0d0;") + main_layout.addWidget(line4) + + # === Custom Loading Parameters Checkbox === + self.custom_params_checkbox = QCheckBox("Tabulate Custom Loading Parameters") + self.custom_params_checkbox.setStyleSheet(""" + QCheckBox { + font-size: 11px; + color: black; + spacing: 8px; + } + QCheckBox::indicator { + width: 18px; + height: 18px; + border: 2px solid #b0b0b0; + border-radius: 3px; + background-color: white; + } + QCheckBox::indicator:checked { + background-color: #90AF13; + border-color: #90AF13; + } + QCheckBox::indicator:hover { + border-color: #7a9a12; + } + """) + main_layout.addWidget(self.custom_params_checkbox) + + main_layout.addStretch() + + # === Bottom Buttons === + btn_layout = QHBoxLayout() + btn_layout.addStretch() + + ok_btn = QPushButton("OK") + ok_btn.setStyleSheet(style_main_buttons()) + ok_btn.setMinimumWidth(100) + ok_btn.clicked.connect(dialog.accept) + btn_layout.addWidget(ok_btn) + + cancel_btn = QPushButton("Cancel") + cancel_btn.setStyleSheet(""" + QPushButton { + background-color: white; + color: #333; + border: 1px solid #c0c0c0; + border-radius: 3px; + padding: 6px 16px; + min-height: 28px; + } + QPushButton:hover { + background-color: #f5f5f5; + } + """) + cancel_btn.setMinimumWidth(100) + cancel_btn.clicked.connect(dialog.reject) + btn_layout.addWidget(cancel_btn) + + main_layout.addLayout(btn_layout) + + # Function to update districts based on selected state + def on_state_changed(state_name): + districts = state_districts.get(state_name, ["Select District"]) + self.district_combo.clear() + self.district_combo.addItems(districts) + + # Function to handle map checkbox + def on_map_checkbox_changed(state): + enabled = (state == 2) + self.map_placeholder.setEnabled(enabled) + if enabled: + self.map_placeholder.setStyleSheet(""" + QLabel { + border: 2px solid #90AF13; + background-color: white; + padding: 20px; + color: #666666; + } + """) + self.map_placeholder.setText("Map Placeholder\n(Click to select location)\n(Will be implemented later)") + else: + self.map_placeholder.setStyleSheet(""" + QLabel { + border: 1px solid #e0e0e0; + background-color: #f5f5f5; + padding: 20px; + color: #999999; + } + """) + self.map_placeholder.setText("Map Placeholder\n(Will be added later)") + + # Connect checkbox signals to enable/disable fields + self.coords_checkbox.stateChanged.connect(lambda state: self.latitude_input.setEnabled(state == 2) or self.longitude_input.setEnabled(state == 2)) + self.location_checkbox.stateChanged.connect(lambda state: self.state_combo.setEnabled(state == 2) or self.district_combo.setEnabled(state == 2)) + self.map_checkbox.stateChanged.connect(on_map_checkbox_changed) + + # Connect state combo to update districts + self.state_combo.currentTextChanged.connect(on_state_changed) + + if dialog.exec() == QDialog.Accepted: + pass + def build_left_panel(self, field_list): left_layout = QVBoxLayout(self.left_container) left_layout.setContentsMargins(0, 0, 0, 0) @@ -235,13 +612,13 @@ def build_left_panel(self, field_list): self.left_panel = QWidget() self.left_panel.setStyleSheet("background-color: white;") panel_layout = QVBoxLayout(self.left_panel) - panel_layout.setContentsMargins(8, 8, 8, 8) + panel_layout.setContentsMargins(15, 10, 15, 10) panel_layout.setSpacing(0) # Top Bar with buttons top_bar = QHBoxLayout() top_bar.setSpacing(8) - top_bar.setContentsMargins(0, 0, 0, 10) + top_bar.setContentsMargins(0, 0, 0, 15) input_dock_btn = QPushButton("Basic Inputs") input_dock_btn.setStyleSheet(style_main_buttons()) @@ -254,14 +631,14 @@ def build_left_panel(self, field_list): background-color: white; color: #333; font-weight: normal; - border: 1px solid #c0c0c0; + border: 1px solid #b0b0b0; border-radius: 3px; padding: 6px 16px; min-height: 28px; } QPushButton:hover { background-color: #f5f5f5; - border: 1px solid #a0a0a0; + border: 1px solid #909090; } """) additional_inputs_btn.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) @@ -305,136 +682,204 @@ def build_left_panel(self, field_list): group_container_layout = QVBoxLayout(group_container) group_container_layout.setContentsMargins(0, 0, 0, 0) group_container_layout.setSpacing(12) - - # Build form - track_group = False - index = 0 - for field in field_list: - index += 1 - label = field[1] - type = field[2] - - if type == TYPE_MODULE: - continue - elif type == TYPE_TITLE: - if track_group: - current_group.setLayout(cur_box_form) - group_container_layout.addWidget(current_group) - track_group = False - - current_group = QGroupBox(label) - current_group.setObjectName(label + "_group") - track_group = True - current_group.setStyleSheet(""" - QGroupBox { - border: 1px solid #d0d0d0; - border-radius: 4px; - margin-top: 8px; - font-weight: bold; - font-size: 11px; - padding-top: 8px; - } - QGroupBox::title { - subcontrol-origin: margin; - subcontrol-position: top left; - left: 8px; - padding: 0 4px; - background-color: white; - color: #333; - } - """) - cur_box_form = QFormLayout() - cur_box_form.setHorizontalSpacing(8) - cur_box_form.setVerticalSpacing(12) - cur_box_form.setContentsMargins(12, 16, 12, 12) - cur_box_form.setLabelAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter) - cur_box_form.setFieldGrowthPolicy(QFormLayout.ExpandingFieldsGrow) - - elif type == TYPE_COMBOBOX or type == TYPE_COMBOBOX_CUSTOMIZED: - left = QLabel(label) - left.setObjectName(field[0] + "_label") - left.setStyleSheet("font-weight: normal; color: #333; font-size: 10px;") - - right = NoScrollComboBox() - right.setObjectName(field[0]) - apply_field_style(right) - option_list = field[3] - right.addItems(option_list) - - # Connect signal for Structure Type combo box - if field[0] == KEY_STRUCTURE_TYPE: - self.structure_type_combo = right - right.currentTextChanged.connect(self.on_structure_type_changed) - right.setToolTip("Defines the application of the steel girder bridge.\nCurrently only Highway Bridge is supported.") - - # Connect signal and set default for Project Location combo box - elif field[0] == KEY_PROJECT_LOCATION: - self.project_location_combo = right - right.currentTextChanged.connect(self.on_project_location_changed) - right.setToolTip("Select city in India for load calculations.\nSelect 'Custom' to enter a different location.") - - # Set default value for Footpath combo box - elif field[0] == KEY_FOOTPATH: - # Store reference and set default to "None" (first item in VALUES_FOOTPATH) - self.footpath_combo = right - right.setCurrentIndex(0) - right.setToolTip("Select footpath configuration.\nIRC 5 Clause 101.41: Safety kerb required when footpath is not present.") - # Connect to update additional inputs if open - right.currentTextChanged.connect(self.on_footpath_changed) - - # Set default value for Deck Concrete Grade - elif field[0] == KEY_DECK_CONCRETE_GRADE_BASIC: - right.setCurrentText("M25") # Set default to M25 - right.setToolTip("Select concrete grade for bridge deck.\nMinimum M25 grade required for bridge deck construction.") - - cur_box_form.addRow(left, right_aligned_widget(right)) - - elif type == TYPE_IMAGE: - left = "" - right = QLabel() - right.setFixedWidth(90) - right.setFixedHeight(90) - right.setObjectName(field[0]) - right.setScaledContents(True) - try: - pixmap = QPixmap(field[3]) - if not pixmap.isNull(): - right.setPixmap(pixmap) - except: - right.setText("Image") - right.setAlignment(Qt.AlignmentFlag.AlignLeft) - cur_box_form.addRow(left, left_aligned_widget(right)) - - elif type == TYPE_TEXTBOX: - left = QLabel(label) - left.setObjectName(field[0] + "_label") - left.setStyleSheet("font-weight: normal; color: #333; font-size: 10px;") - - right = QLineEdit() - apply_field_style(right) - right.setObjectName(field[0]) - right.setEnabled(True if field[4] else False) - if field[5] != 'No Validator': - right.setValidator(self.get_validator(field[5])) - - # Set default value and tooltips for specific fields - if field[0] == KEY_SKEW_ANGLE: - right.setText(str(SKEW_ANGLE_DEFAULT)) - right.setPlaceholderText(f"Default: {SKEW_ANGLE_DEFAULT}°") - right.setToolTip(f"Skew angle of rolled beams or plate girders.\nIRC 24 (2010) requires detailed analysis when skew angle exceeds ±15°.\nRange: {SKEW_ANGLE_MIN}° to {SKEW_ANGLE_MAX}°") - elif field[0] == KEY_SPAN: - right.setPlaceholderText(f"Enter value between {SPAN_MIN}-{SPAN_MAX} m") - right.setToolTip(f"Total length of the steel girder bridge.\nMust be between {SPAN_MIN} m and {SPAN_MAX} m") - elif field[0] == KEY_CARRIAGEWAY_WIDTH: - right.setPlaceholderText(f"Min {CARRIAGEWAY_WIDTH_MIN} m") - right.setToolTip(f"Width of bridge deck surface from curb to curb.\nIRC 5 Clause 104.3.1 requires minimum {CARRIAGEWAY_WIDTH_MIN} m") - - cur_box_form.addRow(left, right_aligned_widget(right)) - - if index == len(field_list): - current_group.setLayout(cur_box_form) - group_container_layout.addWidget(current_group) - + + # === Type of Structure Section === + structure_group = create_group_box("Type of Structure") + structure_layout = QVBoxLayout() + structure_layout.setContentsMargins(10, 15, 10, 10) + structure_layout.setSpacing(8) + + self.structure_type_combo = NoScrollComboBox() + self.structure_type_combo.setObjectName(KEY_STRUCTURE_TYPE) + apply_field_style(self.structure_type_combo) + self.structure_type_combo.addItems(VALUES_STRUCTURE_TYPE) + self.structure_type_combo.setToolTip("Defines the application of the steel girder bridge.\nCurrently only Highway Bridge is supported.") + + structure_row = create_form_row("Type of structure", self.structure_type_combo) + structure_layout.addLayout(structure_row) + + self.structure_note = QLabel("*Other structures not included") + self.structure_note.setStyleSheet("font-size: 9px; color: #d32f2f; font-style: italic; margin-left: 150px;") + self.structure_note.setVisible(False) + structure_layout.addWidget(self.structure_note) + + self.structure_type_combo.currentTextChanged.connect(self.on_structure_type_changed) + + structure_group.setLayout(structure_layout) + group_container_layout.addWidget(structure_group) + + # === Project Location Section === + location_group = QGroupBox() + location_group.setStyleSheet(""" + QGroupBox { + border: 1px solid #d0d0d0; + border-radius: 5px; + margin-top: 0px; + padding-top: 10px; + background-color: #fafafa; + } + """) + location_layout = QVBoxLayout() + location_layout.setContentsMargins(10, 10, 10, 10) + location_layout.setSpacing(0) + + self.project_location_combo = NoScrollComboBox() + self.project_location_combo.setObjectName(KEY_PROJECT_LOCATION) + self.project_location_combo.addItems(VALUES_PROJECT_LOCATION) + self.project_location_combo.currentTextChanged.connect(self.on_project_location_changed) + self.project_location_combo.hide() + + location_btn = QPushButton("Project Location") + location_btn.setStyleSheet(""" + QPushButton { + background-color: white; + color: #333; + border: 1px solid #b0b0b0; + border-radius: 3px; + padding: 8px 12px; + text-align: left; + min-height: 28px; + padding-right: 30px; + } + QPushButton:hover { + background-color: #f5f5f5; + border: 1px solid #909090; + } + QPushButton::menu-indicator { + image: none; + width: 0px; + } + """) + + # Add dropdown arrow indicator + location_btn_container = QWidget() + location_btn_layout = QHBoxLayout(location_btn_container) + location_btn_layout.setContentsMargins(0, 0, 0, 0) + location_btn_layout.setSpacing(0) + + location_btn.clicked.connect(self.show_project_location_dialog) + location_btn_layout.addWidget(location_btn) + + # Create arrow overlay + arrow_label = QLabel("▼") + arrow_label.setAlignment(Qt.AlignCenter) + arrow_label.setStyleSheet(""" + QLabel { + color: #606060; + font-size: 8px; + background-color: transparent; + border: 1px solid #606060; + border-radius: 10px; + min-width: 20px; + max-width: 20px; + min-height: 20px; + max-height: 20px; + margin-right: 8px; + } + QLabel:hover { + background-color: #e0e0e0; + } + """) + + # We don't need arrow_label.setFixedWidth(20) anymore + location_btn_layout.addWidget(arrow_label) + location_btn_layout.setAlignment(arrow_label, Qt.AlignRight | Qt.AlignVCenter) + + # Position arrow on top of button + arrow_label.raise_() + arrow_label.setAttribute(Qt.WA_TransparentForMouseEvents) + + location_layout.addWidget(location_btn) + + location_group.setLayout(location_layout) + group_container_layout.addWidget(location_group) + + # === Geometric Details Section === + geometric_group = create_group_box("Geometric Details") + geometric_layout = QVBoxLayout() + geometric_layout.setContentsMargins(10, 15, 10, 10) + geometric_layout.setSpacing(8) + + # Span + self.span_input = QLineEdit() + self.span_input.setObjectName(KEY_SPAN) + apply_field_style(self.span_input) + self.span_input.setValidator(QDoubleValidator(SPAN_MIN, SPAN_MAX, 2)) + self.span_input.setPlaceholderText(f"{SPAN_MIN}-{SPAN_MAX} m") + span_row = create_form_row("Span (m):", self.span_input, + f"Total length of the steel girder bridge.\nMust be between {SPAN_MIN} m and {SPAN_MAX} m") + geometric_layout.addLayout(span_row) + + # Carriageway Width + self.carriageway_input = QLineEdit() + self.carriageway_input.setObjectName(KEY_CARRIAGEWAY_WIDTH) + apply_field_style(self.carriageway_input) + self.carriageway_input.setValidator(QDoubleValidator(CARRIAGEWAY_WIDTH_MIN, 100.0, 2)) + self.carriageway_input.setPlaceholderText(f"Min {CARRIAGEWAY_WIDTH_MIN} m") + carriageway_row = create_form_row("Carriageway (m):", self.carriageway_input, + f"Width of bridge deck surface from curb to curb.\nIRC 5 Clause 104.3.1 requires minimum {CARRIAGEWAY_WIDTH_MIN} m") + geometric_layout.addLayout(carriageway_row) + + # Footpath + self.footpath_combo = NoScrollComboBox() + self.footpath_combo.setObjectName(KEY_FOOTPATH) + apply_field_style(self.footpath_combo) + self.footpath_combo.addItems(VALUES_FOOTPATH) + self.footpath_combo.setCurrentIndex(0) + footpath_row = create_form_row("Footpath:", self.footpath_combo, + "Select footpath configuration.\nIRC 5 Clause 101.41: Safety kerb required when footpath is not present.") + self.footpath_combo.currentTextChanged.connect(self.on_footpath_changed) + geometric_layout.addLayout(footpath_row) + + # Skew Angle + self.skew_input = QLineEdit() + self.skew_input.setObjectName(KEY_SKEW_ANGLE) + apply_field_style(self.skew_input) + self.skew_input.setValidator(QDoubleValidator(SKEW_ANGLE_MIN, SKEW_ANGLE_MAX, 1)) + self.skew_input.setText(str(SKEW_ANGLE_DEFAULT)) + self.skew_input.setPlaceholderText(f"Default: {SKEW_ANGLE_DEFAULT}°") + skew_row = create_form_row("Skew Angle (°):", self.skew_input, + f"Skew angle of rolled beams or plate girders.\nIRC 24 (2010) requires detailed analysis when skew angle exceeds ±15°.\nRange: {SKEW_ANGLE_MIN}° to {SKEW_ANGLE_MAX}°") + geometric_layout.addLayout(skew_row) + + geometric_group.setLayout(geometric_layout) + group_container_layout.addWidget(geometric_group) + + # === Material Inputs Section === + material_group = create_group_box("Material Inputs") + material_layout = QVBoxLayout() + material_layout.setContentsMargins(10, 15, 10, 10) + material_layout.setSpacing(8) + + # Girder + self.girder_combo = NoScrollComboBox() + self.girder_combo.setObjectName(KEY_GIRDER) + apply_field_style(self.girder_combo) + self.girder_combo.addItems(VALUES_MATERIAL) + girder_row = create_form_row("Girder:", self.girder_combo) + material_layout.addLayout(girder_row) + + # Cross Bracing + self.cross_bracing_combo = NoScrollComboBox() + self.cross_bracing_combo.setObjectName(KEY_CROSS_BRACING) + apply_field_style(self.cross_bracing_combo) + self.cross_bracing_combo.addItems(VALUES_MATERIAL) + cross_bracing_row = create_form_row("Cross Bracing:", self.cross_bracing_combo) + material_layout.addLayout(cross_bracing_row) + + # Deck + self.deck_combo = NoScrollComboBox() + self.deck_combo.setObjectName(KEY_DECK_CONCRETE_GRADE_BASIC) + apply_field_style(self.deck_combo) + self.deck_combo.addItems(VALUES_DECK_CONCRETE_GRADE) + self.deck_combo.setCurrentText("M25") + deck_row = create_form_row("Deck:", self.deck_combo, + "Select concrete grade for bridge deck.\nMinimum M25 grade required for bridge deck construction.") + material_layout.addLayout(deck_row) + + material_group.setLayout(material_layout) + group_container_layout.addWidget(material_group) + group_container_layout.addStretch() scroll_area.setWidget(group_container) @@ -485,11 +930,9 @@ def build_left_panel(self, field_list): def show_additional_inputs(self): """Show Additional Inputs dialog""" - # Get current footpath value footpath_value = self.footpath_combo.currentText() if self.footpath_combo else "None" - # Get carriageway width from basic inputs - carriageway_width = 7.5 # Default value + carriageway_width = 7.5 if self.input_widget: carriageway_field = self.input_widget.findChild(QLineEdit, KEY_CARRIAGEWAY_WIDTH) if carriageway_field and carriageway_field.text(): @@ -498,7 +941,6 @@ def show_additional_inputs(self): except ValueError: carriageway_width = 7.5 - # Create or show existing window if self.additional_inputs_window is None or not self.additional_inputs_window.isVisible(): self.additional_inputs_window = QDialog(self) self.additional_inputs_window.setWindowTitle("Additional Inputs - Manual Bridge Parameter Definition") @@ -507,7 +949,6 @@ def show_additional_inputs(self): layout = QVBoxLayout(self.additional_inputs_window) layout.setContentsMargins(0, 0, 0, 0) - # Add the additional inputs widget with carriageway width self.additional_inputs_widget = AdditionalInputsWidget(footpath_value, carriageway_width, self.additional_inputs_window) layout.addWidget(self.additional_inputs_widget) From f9bf2300aa53dd45385a79aa491e3bacadbd9b1f Mon Sep 17 00:00:00 2001 From: Garvit Singh Rathore <78960005+garvit000@users.noreply.github.com> Date: Tue, 28 Oct 2025 16:29:35 +0530 Subject: [PATCH 18/59] Enhance README with installation and usage sections Added installation instructions and usage details to README. --- README.md | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/README.md b/README.md index c4d69f68..51f7fb97 100644 --- a/README.md +++ b/README.md @@ -22,3 +22,39 @@ OsBridge/ ├── README.md └── requirements.txt ``` +## Installation + +### Prerequisites +- Python 3.8 or higher +- pip package manager + +### Setup + +1. Clone the repository: +```bash +git clone https://github.com/garvit000/osdag_bridge.git +cd osdag_bridge +``` + +2. Install dependencies: +```bash +pip install -r requirements.txt +``` + +### Dependencies +``` +PySide6>=6.5.0 +``` +## Usage + +### Running the Application + +From the project root directory: +```bash +python src/osbridge/template_page.py +``` + +Or from within the osbridge directory: +```bash +cd src/osbridge +python template_page.py From 1ee39d70a2575694ac931a92cd5889e353100cde Mon Sep 17 00:00:00 2001 From: Garvit S Rathore Date: Tue, 28 Oct 2025 16:30:05 +0530 Subject: [PATCH 19/59] Change to dropdown selector to svg --- .../additional_inputs.cpython-311.pyc | Bin 84803 -> 78683 bytes src/osbridge/additional_inputs.py | 1473 ++++++----------- 2 files changed, 550 insertions(+), 923 deletions(-) diff --git a/src/osbridge/__pycache__/additional_inputs.cpython-311.pyc b/src/osbridge/__pycache__/additional_inputs.cpython-311.pyc index 2d5b72dfacb601afb38903324ade5d58d4d3ed19..f8fb34e663aaaa760ce2afcc7d28532c70f2cb17 100644 GIT binary patch literal 78683 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 E0an*jUH||9 diff --git a/src/osbridge/additional_inputs.py b/src/osbridge/additional_inputs.py index f9f9c1d7..d5fba952 100644 --- a/src/osbridge/additional_inputs.py +++ b/src/osbridge/additional_inputs.py @@ -3,10 +3,12 @@ Provides detailed input fields for manual bridge parameter definition """ import sys +import base64 from PySide6.QtWidgets import ( QWidget, QVBoxLayout, QHBoxLayout, QTabWidget, QLabel, QLineEdit, QComboBox, QGroupBox, QFormLayout, QPushButton, QScrollArea, - QCheckBox, QMessageBox, QSizePolicy, QSpacerItem + QCheckBox, QMessageBox, QSizePolicy, QSpacerItem, QStackedWidget, + QFrame, QGridLayout ) from PySide6.QtCore import Qt, Signal from PySide6.QtGui import QDoubleValidator, QIntValidator @@ -14,6 +16,129 @@ from common import * +def get_dropdown_svg_icon(): + """Return the SVG markup for styled dropdown arrows.""" + return ( + "" + "" + "" + "" + ) + + +def get_combobox_style(): + """Return the common stylesheet for dropdowns with the SVG icon.""" + svg_data = get_dropdown_svg_icon().encode("utf-8") + svg_base64 = base64.b64encode(svg_data).decode("utf-8") + return f""" + QComboBox {{ + padding: 6px 42px 6px 14px; + border: 1px solid #b8b8b8; + border-radius: 8px; + background-color: #ffffff; + color: #2b2b2b; + font-size: 12px; + min-height: 34px; + }} + QComboBox:hover {{ + border: 1px solid #909090; + }} + QComboBox:focus {{ + border: 1px solid #4a7ba7; + }} + QComboBox::drop-down {{ + subcontrol-origin: padding; + subcontrol-position: center right; + width: 30px; + border: none; + background: transparent; + right: 8px; + }} + QComboBox::down-arrow {{ + image: url(data:image/svg+xml;base64,{svg_base64}); + width: 26px; + height: 26px; + }} + QComboBox QAbstractItemView {{ + border: 1px solid #b8b8b8; + background: #ffffff; + selection-background-color: #e7f2ff; + selection-color: #1f1f1f; + }} + QComboBox QAbstractItemView::item {{ + padding: 6px 10px; + font-size: 12px; + }} + """ + + +def get_lineedit_style(): + """Return the shared stylesheet for line edits in the section inputs.""" + return """ + QLineEdit { + padding: 6px 12px; + border: 1px solid #b8b8b8; + border-radius: 8px; + background-color: #ffffff; + color: #2b2b2b; + font-size: 12px; + min-height: 34px; + } + QLineEdit:hover { + border: 1px solid #909090; + } + QLineEdit:focus { + border: 1px solid #4a7ba7; + } + QLineEdit:disabled { + background-color: #f0f0f0; + color: #9b9b9b; + } + """ + + +def apply_field_style(widget): + """Apply the appropriate style to combo boxes and line edits.""" + widget.setMinimumHeight(34) + if isinstance(widget, QComboBox): + widget.setStyleSheet(get_combobox_style()) + elif isinstance(widget, QLineEdit): + widget.setStyleSheet(get_lineedit_style()) + + +SECTION_NAV_BUTTON_STYLE = """ + QPushButton { + background-color: #f4f4f4; + border: 2px solid #d2d2d2; + border-radius: 12px; + padding: 20px 16px; + text-align: left; + font-weight: bold; + font-size: 12px; + color: #333333; + } + QPushButton:hover { + border-color: #b5b5b5; + } + QPushButton:checked { + background-color: #9ecb3d; + border-color: #7da523; + color: #ffffff; + } +""" + + class OptimizableField(QWidget): """Widget that allows selection between Optimized/Customized/All modes with input field""" @@ -21,29 +146,36 @@ def __init__(self, label_text, parent=None): super().__init__(parent) self.layout = QHBoxLayout(self) self.layout.setContentsMargins(0, 0, 0, 0) + self.layout.setSpacing(8) # Mode selector self.mode_combo = QComboBox() self.mode_combo.addItems(VALUES_OPTIMIZATION_MODE) - self.mode_combo.setMaximumWidth(120) + self.mode_combo.setMinimumWidth(140) + self.mode_combo.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) # Input field self.input_field = QLineEdit() self.input_field.setEnabled(False) # Disabled by default for "Optimized" + self.input_field.setVisible(False) + self.input_field.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) self.layout.addWidget(self.mode_combo) self.layout.addWidget(self.input_field) # Connect signal self.mode_combo.currentTextChanged.connect(self.on_mode_changed) + self.on_mode_changed(self.mode_combo.currentText()) def on_mode_changed(self, text): """Enable/disable input field based on selection""" - if text == "Optimized": + if text in ("Optimized", "All"): self.input_field.setEnabled(False) self.input_field.clear() + self.input_field.setVisible(False) else: self.input_field.setEnabled(True) + self.input_field.setVisible(True) def get_value(self): """Returns tuple of (mode, value)""" @@ -64,75 +196,7 @@ def __init__(self, footpath_value="None", carriageway_width=7.5, parent=None): def style_input_field(self, field): """Apply consistent styling to input fields""" - field.setMinimumHeight(28) - if isinstance(field, QComboBox): - field.setStyleSheet(""" - QComboBox { - padding: 4px 8px; - padding-right: 30px; - border: 1px solid #c0c0c0; - border-radius: 3px; - background-color: white; - color: #333; - } - QComboBox::drop-down { - subcontrol-origin: padding; - subcontrol-position: center right; - width: 18px; - height: 18px; - border: 1px solid #606060; - border-radius: 9px; - background-color: transparent; - right: 5px; - } - QComboBox::drop-down:hover { - background-color: #e0e0e0; - } - QComboBox::down-arrow { - image: none; - width: 0; - height: 0; - border-left: 3px solid transparent; - border-right: 3px solid transparent; - border-top: 4px solid #606060; - } - QComboBox:focus { - border: 1px solid #4a7ba7; - } - QComboBox QAbstractItemView { - background-color: white; - border: 1px solid #c0c0c0; - outline: none; - selection-background-color: #e8f4ff; - } - QComboBox QAbstractItemView::item { - color: black; - background-color: white; - padding: 4px; - min-height: 20px; - } - QComboBox QAbstractItemView::item:hover { - background-color: #e8f4ff; - color: black; - } - """) - else: - field.setStyleSheet(""" - QLineEdit { - padding: 4px 8px; - border: 1px solid #c0c0c0; - border-radius: 3px; - background-color: white; - color: #333; - } - QLineEdit:focus { - border: 1px solid #4a7ba7; - } - QLineEdit:disabled { - background-color: #f0f0f0; - color: #999; - } - """) + apply_field_style(field) def style_group_box(self, group_box): """Apply consistent styling to group boxes""" @@ -564,53 +628,92 @@ def on_crash_barrier_type_changed(self, barrier_type): class SectionPropertiesTab(QWidget): - """Sub-tab for Section Properties with nested tabs for Girder, Stiffener, Cross-Bracing, and End Diaphragm""" - + """Sub-tab for Section Properties with custom navigation layout.""" + def __init__(self, parent=None): super().__init__(parent) + self.nav_buttons = [] self.init_ui() - + def init_ui(self): - """Initialize the UI""" + """Initialize styled navigation and content panels.""" + self.setStyleSheet("background-color: #f7f7f7;") main_layout = QVBoxLayout(self) - main_layout.setContentsMargins(10, 10, 10, 10) - main_layout.setSpacing(10) - - # Create nested tab widget for different section types - self.section_tabs = QTabWidget() - self.section_tabs.setStyleSheet(""" - QTabWidget::pane { - border: 1px solid #d0d0d0; - background: white; - } - QTabBar::tab { - background: #f0f0f0; - color: black; - border: 1px solid #d0d0d0; - padding: 6px 12px; - margin-right: 2px; - } - QTabBar::tab:selected { - background: #A2EBB6; - color: black; - font-weight: bold; - } - """) - - # Add nested tabs - self.girder_tab = GirderDetailsTab() - self.section_tabs.addTab(self.girder_tab, "Girder Details") - - self.stiffener_tab = StiffenerDetailsTab() - self.section_tabs.addTab(self.stiffener_tab, "Stiffener Details") - - self.cross_bracing_tab = CrossBracingDetailsTab() - self.section_tabs.addTab(self.cross_bracing_tab, "Cross-Bracing Details") - - self.end_diaphragm_tab = EndDiaphragmDetailsTab() - self.section_tabs.addTab(self.end_diaphragm_tab, "End Diaphragm Details") - - main_layout.addWidget(self.section_tabs) + main_layout.setContentsMargins(16, 16, 16, 16) + main_layout.setSpacing(16) + + content_layout = QHBoxLayout() + content_layout.setSpacing(22) + main_layout.addLayout(content_layout) + + nav_frame = QFrame() + nav_frame.setObjectName("sectionNavFrame") + nav_frame.setFixedWidth(190) + nav_frame.setStyleSheet( + "QFrame#sectionNavFrame {" + " background-color: #f2f2f2;" + " border: 1px solid #d1d1d1;" + " border-radius: 16px;" + "}" + ) + nav_layout = QVBoxLayout(nav_frame) + nav_layout.setContentsMargins(14, 20, 14, 20) + nav_layout.setSpacing(14) + content_layout.addWidget(nav_frame) + + content_frame = QFrame() + content_frame.setObjectName("sectionContentFrame") + content_frame.setStyleSheet( + "QFrame#sectionContentFrame {" + " background-color: #ffffff;" + " border: 1px solid #c7c7c7;" + " border-radius: 18px;" + "}" + ) + content_inner_layout = QVBoxLayout(content_frame) + content_inner_layout.setContentsMargins(30, 24, 30, 24) + content_inner_layout.setSpacing(0) + + self.stack = QStackedWidget() + self.stack.setObjectName("sectionStack") + self.stack.setStyleSheet("QStackedWidget#sectionStack { background-color: transparent; }") + content_inner_layout.addWidget(self.stack) + + content_layout.addWidget(content_frame, 1) + content_layout.setStretch(0, 0) + content_layout.setStretch(1, 1) + + sections = [ + ("Girder Details", GirderDetailsTab), + ("Stiffener Details", StiffenerDetailsTab), + ("Cross-Bracing Details", CrossBracingDetailsTab), + ("End Diaphragm Details", EndDiaphragmDetailsTab), + ] + + for index, (title, widget_cls) in enumerate(sections): + button = QPushButton(f"{title}:") + button.setCheckable(True) + button.setCursor(Qt.PointingHandCursor) + button.setStyleSheet(SECTION_NAV_BUTTON_STYLE) + button.setMinimumHeight(86) + button.clicked.connect(lambda _checked, idx=index: self.switch_section(idx)) + nav_layout.addWidget(button) + self.nav_buttons.append(button) + + section_widget = widget_cls() + self.stack.addWidget(section_widget) + + nav_layout.addStretch() + + if self.nav_buttons: + self.nav_buttons[0].setChecked(True) + self.stack.setCurrentIndex(0) + + def switch_section(self, index): + """Switch the stacked widget page and update navigation states.""" + self.stack.setCurrentIndex(index) + for btn_index, button in enumerate(self.nav_buttons): + button.setChecked(btn_index == index) class GirderDetailsTab(QWidget): @@ -618,52 +721,54 @@ class GirderDetailsTab(QWidget): def __init__(self, parent=None): super().__init__(parent) + self.plate_rows = [] self.init_ui() def init_ui(self): """Initialize the UI""" main_layout = QVBoxLayout(self) - main_layout.setContentsMargins(10, 10, 10, 10) - main_layout.setSpacing(10) - - # Scroll area + main_layout.setContentsMargins(0, 0, 0, 0) + main_layout.setSpacing(0) + scroll = QScrollArea() scroll.setWidgetResizable(True) + scroll.setFrameShape(QFrame.NoFrame) scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) - - scroll_widget = QWidget() - scroll_layout = QVBoxLayout(scroll_widget) - scroll_layout.setSpacing(15) - - # Girder Type Selection - type_group = QGroupBox("Girder Type") - type_group.setStyleSheet(self.get_group_style()) - type_layout = QVBoxLayout() - - type_row = QHBoxLayout() - type_label = QLabel("Girder Type:") - type_label.setStyleSheet("font-size: 11px; min-width: 200px;") + scroll.setStyleSheet( + "QScrollArea { border: none; background: transparent; }" + "QScrollArea > QWidget > QWidget { background: transparent; }" + ) + main_layout.addWidget(scroll) + + container = QWidget() + scroll.setWidget(container) + + container_layout = QVBoxLayout(container) + container_layout.setContentsMargins(0, 0, 0, 0) + container_layout.setSpacing(0) + + form_frame = QFrame() + form_frame.setStyleSheet("QFrame { background: transparent; }") + self.form_layout = QGridLayout(form_frame) + self.form_layout.setContentsMargins(0, 0, 0, 0) + self.form_layout.setHorizontalSpacing(28) + self.form_layout.setVerticalSpacing(20) + self.form_layout.setColumnMinimumWidth(0, 220) + self.form_layout.setColumnStretch(1, 1) + container_layout.addWidget(form_frame) + container_layout.addStretch() + + row = 0 + self.girder_type_label = self.create_label("Girder Type:") self.girder_type_combo = QComboBox() self.girder_type_combo.addItems(VALUES_GIRDER_TYPE) - self.style_input_field(self.girder_type_combo) - type_row.addWidget(type_label) - type_row.addWidget(self.girder_type_combo) - type_row.addStretch() - type_layout.addLayout(type_row) - - type_group.setLayout(type_layout) - scroll_layout.addWidget(type_group) - - # IS Rolled Beam Section (visible when IS Standard Rolled Beam is selected) - self.is_beam_group = QGroupBox("IS Standard Rolled Beam Section") - self.is_beam_group.setStyleSheet(self.get_group_style()) - is_beam_layout = QVBoxLayout() - - is_beam_row = QHBoxLayout() - is_beam_label = QLabel("Select IS Beam Section:") - is_beam_label.setStyleSheet("font-size: 11px; min-width: 200px;") + apply_field_style(self.girder_type_combo) + self.form_layout.addWidget(self.girder_type_label, row, 0, Qt.AlignVCenter) + self.form_layout.addWidget(self.girder_type_combo, row, 1) + row += 1 + + self.is_section_label = self.create_label("Select IS Beam Section:") self.is_beam_combo = QComboBox() - # Add sample IS sections - you can expand this list self.is_beam_combo.addItems([ "Select Section", "ISMB 100", "ISMB 125", "ISMB 150", "ISMB 175", "ISMB 200", @@ -672,242 +777,83 @@ def init_ui(self): "ISWB 150", "ISWB 175", "ISWB 200", "ISWB 225", "ISWB 250", "ISWB 300", "ISWB 350", "ISWB 400", "ISWB 450", "ISWB 500", "ISWB 550", "ISWB 600" ]) - self.style_input_field(self.is_beam_combo) - is_beam_row.addWidget(is_beam_label) - is_beam_row.addWidget(self.is_beam_combo) - is_beam_row.addStretch() - is_beam_layout.addLayout(is_beam_row) - - self.is_beam_group.setLayout(is_beam_layout) - scroll_layout.addWidget(self.is_beam_group) - - # Plate Girder Section (visible when Plate Girder is selected) - self.plate_girder_group = QGroupBox("Plate Girder Details") - self.plate_girder_group.setStyleSheet(self.get_group_style()) - plate_layout = QVBoxLayout() - - # Girder Symmetry - symmetry_row = QHBoxLayout() - symmetry_label = QLabel("Girder Symmetry:") - symmetry_label.setStyleSheet("font-size: 11px; min-width: 200px;") + apply_field_style(self.is_beam_combo) + self.form_layout.addWidget(self.is_section_label, row, 0, Qt.AlignVCenter) + self.form_layout.addWidget(self.is_beam_combo, row, 1) + row += 1 + self.symmetry_combo = QComboBox() self.symmetry_combo.addItems(VALUES_GIRDER_SYMMETRY) - self.style_input_field(self.symmetry_combo) - symmetry_row.addWidget(symmetry_label) - symmetry_row.addWidget(self.symmetry_combo) - symmetry_row.addStretch() - plate_layout.addLayout(symmetry_row) - - # Top Flange Width - top_width_row = QHBoxLayout() - top_width_label = QLabel("Top Flange Width (mm):") - top_width_label.setStyleSheet("font-size: 11px; min-width: 200px;") + apply_field_style(self.symmetry_combo) + row = self.add_plate_row(row, "Girder Symmetry", self.symmetry_combo) + self.top_width_field = OptimizableField("Top Flange Width") - self.style_input_field(self.top_width_field.mode_combo) - self.style_input_field(self.top_width_field.input_field) - top_width_row.addWidget(top_width_label) - top_width_row.addWidget(self.top_width_field) - top_width_row.addStretch() - plate_layout.addLayout(top_width_row) - - # Top Flange Thickness - top_thick_row = QHBoxLayout() - top_thick_label = QLabel("Top Flange Thickness (mm):") - top_thick_label.setStyleSheet("font-size: 11px; min-width: 200px;") + self.prepare_optimizable_field(self.top_width_field) + row = self.add_plate_row(row, "Top Flange Width (mm):", self.top_width_field) + self.top_thick_field = OptimizableField("Top Flange Thickness") - self.style_input_field(self.top_thick_field.mode_combo) - self.style_input_field(self.top_thick_field.input_field) - top_thick_row.addWidget(top_thick_label) - top_thick_row.addWidget(self.top_thick_field) - top_thick_row.addStretch() - plate_layout.addLayout(top_thick_row) - - # Bottom Flange Width - bottom_width_row = QHBoxLayout() - bottom_width_label = QLabel("Bottom Flange Width (mm):") - bottom_width_label.setStyleSheet("font-size: 11px; min-width: 200px;") + self.prepare_optimizable_field(self.top_thick_field) + row = self.add_plate_row(row, "Top Flange Thickness (mm):", self.top_thick_field) + self.bottom_width_field = OptimizableField("Bottom Flange Width") - self.style_input_field(self.bottom_width_field.mode_combo) - self.style_input_field(self.bottom_width_field.input_field) - bottom_width_row.addWidget(bottom_width_label) - bottom_width_row.addWidget(self.bottom_width_field) - bottom_width_row.addStretch() - plate_layout.addLayout(bottom_width_row) - - # Bottom Flange Thickness - bottom_thick_row = QHBoxLayout() - bottom_thick_label = QLabel("Bottom Flange Thickness (mm):") - bottom_thick_label.setStyleSheet("font-size: 11px; min-width: 200px;") + self.prepare_optimizable_field(self.bottom_width_field) + row = self.add_plate_row(row, "Bottom Flange Width (mm):", self.bottom_width_field) + self.bottom_thick_field = OptimizableField("Bottom Flange Thickness") - self.style_input_field(self.bottom_thick_field.mode_combo) - self.style_input_field(self.bottom_thick_field.input_field) - bottom_thick_row.addWidget(bottom_thick_label) - bottom_thick_row.addWidget(self.bottom_thick_field) - bottom_thick_row.addStretch() - plate_layout.addLayout(bottom_thick_row) - - # Depth of Section - depth_row = QHBoxLayout() - depth_label = QLabel("Depth of Section (mm):") - depth_label.setStyleSheet("font-size: 11px; min-width: 200px;") + self.prepare_optimizable_field(self.bottom_thick_field) + row = self.add_plate_row(row, "Bottom Flange Thickness (mm):", self.bottom_thick_field) + self.depth_field = OptimizableField("Depth of Section") - self.style_input_field(self.depth_field.mode_combo) - self.style_input_field(self.depth_field.input_field) - depth_row.addWidget(depth_label) - depth_row.addWidget(self.depth_field) - depth_row.addStretch() - plate_layout.addLayout(depth_row) - - # Web Thickness - web_thick_row = QHBoxLayout() - web_thick_label = QLabel("Web Thickness (mm):") - web_thick_label.setStyleSheet("font-size: 11px; min-width: 200px;") + self.prepare_optimizable_field(self.depth_field) + row = self.add_plate_row(row, "Depth of Section (mm):", self.depth_field) + self.web_thick_field = OptimizableField("Web Thickness") - self.style_input_field(self.web_thick_field.mode_combo) - self.style_input_field(self.web_thick_field.input_field) - web_thick_row.addWidget(web_thick_label) - web_thick_row.addWidget(self.web_thick_field) - web_thick_row.addStretch() - plate_layout.addLayout(web_thick_row) - - # Torsional Restraint - torsion_row = QHBoxLayout() - torsion_label = QLabel("Torsional Restraint:") - torsion_label.setStyleSheet("font-size: 11px; min-width: 200px;") + self.prepare_optimizable_field(self.web_thick_field) + row = self.add_plate_row(row, "Web Thickness (mm):", self.web_thick_field) + self.torsion_combo = QComboBox() self.torsion_combo.addItems(VALUES_TORSIONAL_RESTRAINT) - self.style_input_field(self.torsion_combo) - torsion_row.addWidget(torsion_label) - torsion_row.addWidget(self.torsion_combo) - torsion_row.addStretch() - plate_layout.addLayout(torsion_row) - - # Warping Restraint - warp_row = QHBoxLayout() - warp_label = QLabel("Warping Restraint:") - warp_label.setStyleSheet("font-size: 11px; min-width: 200px;") + apply_field_style(self.torsion_combo) + row = self.add_plate_row(row, "Torsional Restraint:", self.torsion_combo) + self.warp_combo = QComboBox() self.warp_combo.addItems(VALUES_WARPING_RESTRAINT) - self.style_input_field(self.warp_combo) - warp_row.addWidget(warp_label) - warp_row.addWidget(self.warp_combo) - warp_row.addStretch() - plate_layout.addLayout(warp_row) - - # Web Type - web_type_row = QHBoxLayout() - web_type_label = QLabel("Web Type:") - web_type_label.setStyleSheet("font-size: 11px; min-width: 200px;") + apply_field_style(self.warp_combo) + row = self.add_plate_row(row, "Warping Restraint:", self.warp_combo) + self.web_type_combo = QComboBox() self.web_type_combo.addItems(VALUES_WEB_TYPE) - self.style_input_field(self.web_type_combo) - web_type_row.addWidget(web_type_label) - web_type_row.addWidget(self.web_type_combo) - web_type_row.addStretch() - plate_layout.addLayout(web_type_row) - - self.plate_girder_group.setLayout(plate_layout) - scroll_layout.addWidget(self.plate_girder_group) - - scroll_layout.addStretch() - scroll.setWidget(scroll_widget) - main_layout.addWidget(scroll) - - # Connect signals + apply_field_style(self.web_type_combo) + row = self.add_plate_row(row, "Web Type* :", self.web_type_combo) + self.girder_type_combo.currentTextChanged.connect(self.on_girder_type_changed) - - # Initialize visibility self.on_girder_type_changed(self.girder_type_combo.currentText()) - + + def create_label(self, text): + label = QLabel(text) + label.setStyleSheet("font-size: 13px; color: #2f2f2f; font-weight: 600;") + return label + + def prepare_optimizable_field(self, field): + field.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) + apply_field_style(field.mode_combo) + apply_field_style(field.input_field) + + def add_plate_row(self, row, text, widget): + label = self.create_label(text) + widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) + self.form_layout.addWidget(label, row, 0, Qt.AlignVCenter) + self.form_layout.addWidget(widget, row, 1) + self.plate_rows.append((label, widget)) + return row + 1 + def on_girder_type_changed(self, text): - """Show/hide sections based on girder type""" - if text == "IS Standard Rolled Beam": - self.is_beam_group.setVisible(True) - self.plate_girder_group.setVisible(False) - else: # Plate Girder - self.is_beam_group.setVisible(False) - self.plate_girder_group.setVisible(True) - - def style_input_field(self, field): - """Apply consistent styling to input fields""" - field.setMinimumHeight(28) - if isinstance(field, QComboBox): - field.setStyleSheet(""" - QComboBox { - padding: 4px 8px; - padding-right: 30px; - border: 1px solid #c0c0c0; - border-radius: 3px; - background-color: white; - color: #333; - } - QComboBox::drop-down { - subcontrol-origin: padding; - subcontrol-position: center right; - width: 18px; - height: 18px; - border: 1px solid #606060; - border-radius: 9px; - background-color: transparent; - right: 5px; - } - QComboBox::drop-down:hover { - background-color: #e0e0e0; - } - QComboBox::down-arrow { - image: none; - width: 0; - height: 0; - border-left: 3px solid transparent; - border-right: 3px solid transparent; - border-top: 4px solid #606060; - } - QComboBox:focus { - border: 1px solid #4a7ba7; - } - QComboBox QAbstractItemView { - background-color: white; - border: 1px solid #c0c0c0; - selection-background-color: #e8f4ff; - } - """) - elif isinstance(field, QLineEdit): - field.setStyleSheet(""" - QLineEdit { - padding: 4px 8px; - border: 1px solid #c0c0c0; - border-radius: 3px; - background-color: white; - color: #333; - } - QLineEdit:focus { - border: 1px solid #4a7ba7; - } - QLineEdit:disabled { - background-color: #f5f5f5; - color: #999; - } - """) - - def get_group_style(self): - """Return consistent group box styling""" - return """ - QGroupBox { - font-weight: bold; - font-size: 11px; - border: 1px solid #d0d0d0; - border-radius: 5px; - margin-top: 10px; - padding-top: 15px; - background-color: #fafafa; - } - QGroupBox::title { - subcontrol-origin: margin; - subcontrol-position: top left; - padding: 0 5px; - background-color: white; - } - """ + is_standard = text == "IS Standard Rolled Beam" + self.is_section_label.setVisible(is_standard) + self.is_beam_combo.setVisible(is_standard) + for label, widget in self.plate_rows: + label.setVisible(not is_standard) + widget.setVisible(not is_standard) class StiffenerDetailsTab(QWidget): @@ -918,186 +864,89 @@ def __init__(self, parent=None): self.init_ui() def init_ui(self): - """Initialize the UI""" main_layout = QVBoxLayout(self) - main_layout.setContentsMargins(10, 10, 10, 10) - main_layout.setSpacing(10) - - # Scroll area + main_layout.setContentsMargins(0, 0, 0, 0) + main_layout.setSpacing(0) + scroll = QScrollArea() scroll.setWidgetResizable(True) + scroll.setFrameShape(QFrame.NoFrame) scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) - - scroll_widget = QWidget() - scroll_layout = QVBoxLayout(scroll_widget) - scroll_layout.setSpacing(15) - - # Stiffener Details Group - stiff_group = QGroupBox("Stiffener Configuration") - stiff_group.setStyleSheet(self.get_group_style()) - stiff_layout = QVBoxLayout() - - # Design Method - method_row = QHBoxLayout() - method_label = QLabel("Stiffener Design Method:") - method_label.setStyleSheet("font-size: 11px; min-width: 220px;") + scroll.setStyleSheet( + "QScrollArea { border: none; background: transparent; }" + "QScrollArea > QWidget > QWidget { background: transparent; }" + ) + main_layout.addWidget(scroll) + + container = QWidget() + scroll.setWidget(container) + + container_layout = QVBoxLayout(container) + container_layout.setContentsMargins(0, 0, 0, 0) + container_layout.setSpacing(0) + + form_frame = QFrame() + form_frame.setStyleSheet("QFrame { background: transparent; }") + self.form_layout = QGridLayout(form_frame) + self.form_layout.setContentsMargins(0, 0, 0, 0) + self.form_layout.setHorizontalSpacing(28) + self.form_layout.setVerticalSpacing(20) + self.form_layout.setColumnMinimumWidth(0, 240) + self.form_layout.setColumnStretch(1, 1) + container_layout.addWidget(form_frame) + container_layout.addStretch() + + row = 0 self.method_combo = QComboBox() self.method_combo.addItems(VALUES_STIFFENER_DESIGN) - self.style_input_field(self.method_combo) - method_row.addWidget(method_label) - method_row.addWidget(self.method_combo) - method_row.addStretch() - stiff_layout.addLayout(method_row) - - # Stiffener Plate Thickness - thick_row = QHBoxLayout() - thick_label = QLabel("Stiffener Plate Thickness (mm):") - thick_label.setStyleSheet("font-size: 11px; min-width: 220px;") + apply_field_style(self.method_combo) + row = self.add_row(row, "Stiffener design method:", self.method_combo) + self.thick_combo = QComboBox() self.thick_combo.addItems(["Optimized", "All"]) - self.style_input_field(self.thick_combo) - thick_row.addWidget(thick_label) - thick_row.addWidget(self.thick_combo) - thick_row.addStretch() - stiff_layout.addLayout(thick_row) - - # Stiffener Spacing - spacing_row = QHBoxLayout() - spacing_label = QLabel("Stiffener Spacing (mm):") - spacing_label.setStyleSheet("font-size: 11px; min-width: 220px;") + apply_field_style(self.thick_combo) + row = self.add_row(row, "Stiffener Plate Thickness (mm):", self.thick_combo) + self.spacing_field = OptimizableField("Stiffener Spacing") self.spacing_field.mode_combo.clear() self.spacing_field.mode_combo.addItems(["Optimized", "Customized"]) - self.style_input_field(self.spacing_field.mode_combo) - self.style_input_field(self.spacing_field.input_field) - spacing_row.addWidget(spacing_label) - spacing_row.addWidget(self.spacing_field) - spacing_row.addStretch() - stiff_layout.addLayout(spacing_row) - - # Longitudinal Stiffener Requirement - long_req_row = QHBoxLayout() - long_req_label = QLabel("Longitudinal Stiffener Requirement:") - long_req_label.setStyleSheet("font-size: 11px; min-width: 220px;") + self.spacing_field.on_mode_changed(self.spacing_field.mode_combo.currentText()) + self.prepare_optimizable_field(self.spacing_field) + row = self.add_row(row, "Stiffener Spacing (mm):", self.spacing_field) + self.long_req_combo = QComboBox() self.long_req_combo.addItems(VALUES_YES_NO) - self.style_input_field(self.long_req_combo) - long_req_row.addWidget(long_req_label) - long_req_row.addWidget(self.long_req_combo) - long_req_row.addStretch() - stiff_layout.addLayout(long_req_row) - - # Longitudinal Stiffener Thickness - long_thick_row = QHBoxLayout() - long_thick_label = QLabel("Longitudinal Stiffener Thickness (mm):") - long_thick_label.setStyleSheet("font-size: 11px; min-width: 220px;") + apply_field_style(self.long_req_combo) + row = self.add_row(row, "Longitudinal stiffener requirement:", self.long_req_combo) + self.long_thick_combo = QComboBox() self.long_thick_combo.addItems(["Optimized", "All"]) - self.long_thick_combo.setEnabled(False) # Disabled by default - self.style_input_field(self.long_thick_combo) - long_thick_row.addWidget(long_thick_label) - long_thick_row.addWidget(self.long_thick_combo) - long_thick_row.addStretch() - stiff_layout.addLayout(long_thick_row) - - stiff_group.setLayout(stiff_layout) - scroll_layout.addWidget(stiff_group) - - scroll_layout.addStretch() - scroll.setWidget(scroll_widget) - main_layout.addWidget(scroll) - - # Connect signals + self.long_thick_combo.setEnabled(False) + apply_field_style(self.long_thick_combo) + self.add_row(row, "Longitudinal stiffener thickness:", self.long_thick_combo) + self.long_req_combo.currentTextChanged.connect(self.on_long_req_changed) - + + def create_label(self, text): + label = QLabel(text) + label.setStyleSheet("font-size: 13px; color: #2f2f2f; font-weight: 600;") + return label + + def add_row(self, row, text, widget): + label = self.create_label(text) + widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) + self.form_layout.addWidget(label, row, 0, Qt.AlignVCenter) + self.form_layout.addWidget(widget, row, 1) + return row + 1 + + def prepare_optimizable_field(self, field): + field.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) + apply_field_style(field.mode_combo) + apply_field_style(field.input_field) + def on_long_req_changed(self, text): """Enable/disable longitudinal stiffener thickness based on requirement""" self.long_thick_combo.setEnabled(text == "Yes") - - def style_input_field(self, field): - """Apply consistent styling to input fields""" - field.setMinimumHeight(28) - if isinstance(field, QComboBox): - field.setStyleSheet(""" - QComboBox { - padding: 4px 8px; - padding-right: 30px; - border: 1px solid #c0c0c0; - border-radius: 3px; - background-color: white; - color: #333; - } - QComboBox::drop-down { - subcontrol-origin: padding; - subcontrol-position: center right; - width: 18px; - height: 18px; - border: 1px solid #606060; - border-radius: 9px; - background-color: transparent; - right: 5px; - } - QComboBox::drop-down:hover { - background-color: #e0e0e0; - } - QComboBox::down-arrow { - image: none; - width: 0; - height: 0; - border-left: 3px solid transparent; - border-right: 3px solid transparent; - border-top: 4px solid #606060; - } - QComboBox:focus { - border: 1px solid #4a7ba7; - } - QComboBox:disabled { - background-color: #f5f5f5; - color: #999; - } - QComboBox QAbstractItemView { - background-color: white; - border: 1px solid #c0c0c0; - selection-background-color: #e8f4ff; - } - """) - elif isinstance(field, QLineEdit): - field.setStyleSheet(""" - QLineEdit { - padding: 4px 8px; - border: 1px solid #c0c0c0; - border-radius: 3px; - background-color: white; - color: #333; - } - QLineEdit:focus { - border: 1px solid #4a7ba7; - } - QLineEdit:disabled { - background-color: #f5f5f5; - color: #999; - } - """) - - def get_group_style(self): - """Return consistent group box styling""" - return """ - QGroupBox { - font-weight: bold; - font-size: 11px; - border: 1px solid #d0d0d0; - border-radius: 5px; - margin-top: 10px; - padding-top: 15px; - background-color: #fafafa; - } - QGroupBox::title { - subcontrol-origin: margin; - subcontrol-position: top left; - padding: 0 5px; - background-color: white; - } - """ class CrossBracingDetailsTab(QWidget): @@ -1108,41 +957,44 @@ def __init__(self, parent=None): self.init_ui() def init_ui(self): - """Initialize the UI""" main_layout = QVBoxLayout(self) - main_layout.setContentsMargins(10, 10, 10, 10) - main_layout.setSpacing(10) - - # Scroll area + main_layout.setContentsMargins(0, 0, 0, 0) + main_layout.setSpacing(0) + scroll = QScrollArea() scroll.setWidgetResizable(True) + scroll.setFrameShape(QFrame.NoFrame) scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) - - scroll_widget = QWidget() - scroll_layout = QVBoxLayout(scroll_widget) - scroll_layout.setSpacing(15) - - # Cross-Bracing Group - bracing_group = QGroupBox("Cross-Bracing Configuration") - bracing_group.setStyleSheet(self.get_group_style()) - bracing_layout = QVBoxLayout() - - # Type of Bracing - type_row = QHBoxLayout() - type_label = QLabel("Type of Bracing:") - type_label.setStyleSheet("font-size: 11px; min-width: 180px;") + scroll.setStyleSheet( + "QScrollArea { border: none; background: transparent; }" + "QScrollArea > QWidget > QWidget { background: transparent; }" + ) + main_layout.addWidget(scroll) + + container = QWidget() + scroll.setWidget(container) + + container_layout = QVBoxLayout(container) + container_layout.setContentsMargins(0, 0, 0, 0) + container_layout.setSpacing(0) + + form_frame = QFrame() + form_frame.setStyleSheet("QFrame { background: transparent; }") + self.form_layout = QGridLayout(form_frame) + self.form_layout.setContentsMargins(0, 0, 0, 0) + self.form_layout.setHorizontalSpacing(28) + self.form_layout.setVerticalSpacing(20) + self.form_layout.setColumnMinimumWidth(0, 210) + self.form_layout.setColumnStretch(1, 1) + container_layout.addWidget(form_frame) + container_layout.addStretch() + + row = 0 self.type_combo = QComboBox() self.type_combo.addItems(VALUES_CROSS_BRACING_TYPE) - self.style_input_field(self.type_combo) - type_row.addWidget(type_label) - type_row.addWidget(self.type_combo) - type_row.addStretch() - bracing_layout.addLayout(type_row) - - # Bracing Section - section_row = QHBoxLayout() - section_label = QLabel("Bracing Section:") - section_label.setStyleSheet("font-size: 11px; min-width: 180px;") + apply_field_style(self.type_combo) + row = self.add_row(row, "Type of Bracing:", self.type_combo) + self.section_combo = QComboBox() self.section_combo.addItems([ "Select Section", @@ -1153,16 +1005,9 @@ def init_ui(self): "ISMC 75", "ISMC 100", "ISMC 125", "ISMC 150", "2-ISMC 75", "2-ISMC 100", "2-ISMC 125" ]) - self.style_input_field(self.section_combo) - section_row.addWidget(section_label) - section_row.addWidget(self.section_combo) - section_row.addStretch() - bracing_layout.addLayout(section_row) - - # Bracket Section - self.bracket_row = QHBoxLayout() - bracket_label = QLabel("Bracket Section:") - bracket_label.setStyleSheet("font-size: 11px; min-width: 180px;") + apply_field_style(self.section_combo) + row = self.add_row(row, "Bracing Section:", self.section_combo) + self.bracket_combo = QComboBox() self.bracket_combo.addItems([ "Select Section", @@ -1173,125 +1018,34 @@ def init_ui(self): "ISMC 75", "ISMC 100", "ISMC 125", "2-ISMC 75", "2-ISMC 100" ]) - self.bracket_combo.setEnabled(False) # Disabled by default - self.style_input_field(self.bracket_combo) - self.bracket_row.addWidget(bracket_label) - self.bracket_row.addWidget(self.bracket_combo) - self.bracket_row.addStretch() - bracing_layout.addLayout(self.bracket_row) - - # Spacing - spacing_row = QHBoxLayout() - spacing_label = QLabel("Spacing (mm):") - spacing_label.setStyleSheet("font-size: 11px; min-width: 180px;") + self.bracket_combo.setEnabled(False) + apply_field_style(self.bracket_combo) + row = self.add_row(row, "Bracket Section:", self.bracket_combo) + self.spacing_input = QLineEdit() self.spacing_input.setPlaceholderText("Enter spacing in mm") self.spacing_input.setValidator(QDoubleValidator(0, 100000, 2)) - self.style_input_field(self.spacing_input) - spacing_row.addWidget(spacing_label) - spacing_row.addWidget(self.spacing_input) - spacing_row.addStretch() - bracing_layout.addLayout(spacing_row) - - bracing_group.setLayout(bracing_layout) - scroll_layout.addWidget(bracing_group) - - scroll_layout.addStretch() - scroll.setWidget(scroll_widget) - main_layout.addWidget(scroll) - - # Connect signals + apply_field_style(self.spacing_input) + self.add_row(row, "Spacing (mm):", self.spacing_input) + self.type_combo.currentTextChanged.connect(self.on_bracing_type_changed) - + + def create_label(self, text): + label = QLabel(text) + label.setStyleSheet("font-size: 13px; color: #2f2f2f; font-weight: 600;") + return label + + def add_row(self, row, text, widget): + label = self.create_label(text) + widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) + self.form_layout.addWidget(label, row, 0, Qt.AlignVCenter) + self.form_layout.addWidget(widget, row, 1) + return row + 1 + def on_bracing_type_changed(self, text): """Enable/disable bracket section based on bracing type""" has_bracket = "bracket" in text.lower() self.bracket_combo.setEnabled(has_bracket) - - def style_input_field(self, field): - """Apply consistent styling to input fields""" - field.setMinimumHeight(28) - if isinstance(field, QComboBox): - field.setStyleSheet(""" - QComboBox { - padding: 4px 8px; - padding-right: 30px; - border: 1px solid #c0c0c0; - border-radius: 3px; - background-color: white; - color: #333; - } - QComboBox::drop-down { - subcontrol-origin: padding; - subcontrol-position: center right; - width: 18px; - height: 18px; - border: 1px solid #606060; - border-radius: 9px; - background-color: transparent; - right: 5px; - } - QComboBox::drop-down:hover { - background-color: #e0e0e0; - } - QComboBox::down-arrow { - image: none; - width: 0; - height: 0; - border-left: 3px solid transparent; - border-right: 3px solid transparent; - border-top: 4px solid #606060; - } - QComboBox:focus { - border: 1px solid #4a7ba7; - } - QComboBox:disabled { - background-color: #f5f5f5; - color: #999; - } - QComboBox QAbstractItemView { - background-color: white; - border: 1px solid #c0c0c0; - selection-background-color: #e8f4ff; - } - """) - elif isinstance(field, QLineEdit): - field.setStyleSheet(""" - QLineEdit { - padding: 4px 8px; - border: 1px solid #c0c0c0; - border-radius: 3px; - background-color: white; - color: #333; - } - QLineEdit:focus { - border: 1px solid #4a7ba7; - } - QLineEdit:disabled { - background-color: #f5f5f5; - color: #999; - } - """) - - def get_group_style(self): - """Return consistent group box styling""" - return """ - QGroupBox { - font-weight: bold; - font-size: 11px; - border: 1px solid #d0d0d0; - border-radius: 5px; - margin-top: 10px; - padding-top: 15px; - background-color: #fafafa; - } - QGroupBox::title { - subcontrol-origin: margin; - subcontrol-position: top left; - padding: 0 5px; - background-color: white; - } - """ class EndDiaphragmDetailsTab(QWidget): @@ -1299,51 +1053,51 @@ class EndDiaphragmDetailsTab(QWidget): def __init__(self, parent=None): super().__init__(parent) + self.plate_rows = [] self.init_ui() def init_ui(self): - """Initialize the UI""" main_layout = QVBoxLayout(self) - main_layout.setContentsMargins(10, 10, 10, 10) - main_layout.setSpacing(10) - - # Scroll area + main_layout.setContentsMargins(0, 0, 0, 0) + main_layout.setSpacing(0) + scroll = QScrollArea() scroll.setWidgetResizable(True) + scroll.setFrameShape(QFrame.NoFrame) scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) - - scroll_widget = QWidget() - scroll_layout = QVBoxLayout(scroll_widget) - scroll_layout.setSpacing(15) - - # End Diaphragm Group - diaphragm_group = QGroupBox("End Diaphragm Configuration") - diaphragm_group.setStyleSheet(self.get_group_style()) - diaphragm_layout = QVBoxLayout() - - # Type of Section - type_row = QHBoxLayout() - type_label = QLabel("Type of Section:") - type_label.setStyleSheet("font-size: 11px; min-width: 200px;") + scroll.setStyleSheet( + "QScrollArea { border: none; background: transparent; }" + "QScrollArea > QWidget > QWidget { background: transparent; }" + ) + main_layout.addWidget(scroll) + + container = QWidget() + scroll.setWidget(container) + + container_layout = QVBoxLayout(container) + container_layout.setContentsMargins(0, 0, 0, 0) + container_layout.setSpacing(0) + + form_frame = QFrame() + form_frame.setStyleSheet("QFrame { background: transparent; }") + self.form_layout = QGridLayout(form_frame) + self.form_layout.setContentsMargins(0, 0, 0, 0) + self.form_layout.setHorizontalSpacing(28) + self.form_layout.setVerticalSpacing(20) + self.form_layout.setColumnMinimumWidth(0, 230) + self.form_layout.setColumnStretch(1, 1) + container_layout.addWidget(form_frame) + container_layout.addStretch() + + row = 0 self.type_combo = QComboBox() self.type_combo.addItems(VALUES_END_DIAPHRAGM_TYPE) - self.style_input_field(self.type_combo) - type_row.addWidget(type_label) - type_row.addWidget(self.type_combo) - type_row.addStretch() - diaphragm_layout.addLayout(type_row) - - diaphragm_group.setLayout(diaphragm_layout) - scroll_layout.addWidget(diaphragm_group) - - # IS Rolled Beam Section - self.is_beam_group = QGroupBox("IS Standard Rolled Beam Section") - self.is_beam_group.setStyleSheet(self.get_group_style()) - is_beam_layout = QVBoxLayout() - - is_beam_row = QHBoxLayout() - is_beam_label = QLabel("Select IS Beam Section:") - is_beam_label.setStyleSheet("font-size: 11px; min-width: 200px;") + apply_field_style(self.type_combo) + self.form_layout.addWidget(self.create_label("Type of Section:"), row, 0, Qt.AlignVCenter) + self.form_layout.addWidget(self.type_combo, row, 1) + row += 1 + + self.is_section_label = self.create_label("Select IS Beam Section:") self.is_beam_combo = QComboBox() self.is_beam_combo.addItems([ "Select Section", @@ -1352,217 +1106,84 @@ def init_ui(self): "ISWB 150", "ISWB 175", "ISWB 200", "ISWB 225", "ISWB 250", "ISWB 300", "ISWB 350", "ISWB 400" ]) - self.style_input_field(self.is_beam_combo) - is_beam_row.addWidget(is_beam_label) - is_beam_row.addWidget(self.is_beam_combo) - is_beam_row.addStretch() - is_beam_layout.addLayout(is_beam_row) - - self.is_beam_group.setLayout(is_beam_layout) - scroll_layout.addWidget(self.is_beam_group) - - # Plate Girder Section - self.plate_girder_group = QGroupBox("Plate Girder Details") - self.plate_girder_group.setStyleSheet(self.get_group_style()) - plate_layout = QVBoxLayout() - - # Top Flange Width - top_width_row = QHBoxLayout() - top_width_label = QLabel("Top Flange Width (mm):") - top_width_label.setStyleSheet("font-size: 11px; min-width: 200px;") + apply_field_style(self.is_beam_combo) + self.form_layout.addWidget(self.is_section_label, row, 0, Qt.AlignVCenter) + self.form_layout.addWidget(self.is_beam_combo, row, 1) + row += 1 + self.top_width_field = OptimizableField("Top Flange Width") - self.style_input_field(self.top_width_field.mode_combo) - self.style_input_field(self.top_width_field.input_field) - top_width_row.addWidget(top_width_label) - top_width_row.addWidget(self.top_width_field) - top_width_row.addStretch() - plate_layout.addLayout(top_width_row) - - # Top Flange Thickness - top_thick_row = QHBoxLayout() - top_thick_label = QLabel("Top Flange Thickness (mm):") - top_thick_label.setStyleSheet("font-size: 11px; min-width: 200px;") + self.prepare_optimizable_field(self.top_width_field) + row = self.add_plate_row(row, "Top Flange Width (mm):", self.top_width_field) + self.top_thick_field = OptimizableField("Top Flange Thickness") - self.style_input_field(self.top_thick_field.mode_combo) - self.style_input_field(self.top_thick_field.input_field) - top_thick_row.addWidget(top_thick_label) - top_thick_row.addWidget(self.top_thick_field) - top_thick_row.addStretch() - plate_layout.addLayout(top_thick_row) - - # Bottom Flange Width - bottom_width_row = QHBoxLayout() - bottom_width_label = QLabel("Bottom Flange Width (mm):") - bottom_width_label.setStyleSheet("font-size: 11px; min-width: 200px;") + self.prepare_optimizable_field(self.top_thick_field) + row = self.add_plate_row(row, "Top Flange Thickness (mm):", self.top_thick_field) + self.bottom_width_field = OptimizableField("Bottom Flange Width") - self.style_input_field(self.bottom_width_field.mode_combo) - self.style_input_field(self.bottom_width_field.input_field) - bottom_width_row.addWidget(bottom_width_label) - bottom_width_row.addWidget(self.bottom_width_field) - bottom_width_row.addStretch() - plate_layout.addLayout(bottom_width_row) - - # Bottom Flange Thickness - bottom_thick_row = QHBoxLayout() - bottom_thick_label = QLabel("Bottom Flange Thickness (mm):") - bottom_thick_label.setStyleSheet("font-size: 11px; min-width: 200px;") + self.prepare_optimizable_field(self.bottom_width_field) + row = self.add_plate_row(row, "Bottom Flange Width (mm):", self.bottom_width_field) + self.bottom_thick_field = OptimizableField("Bottom Flange Thickness") - self.style_input_field(self.bottom_thick_field.mode_combo) - self.style_input_field(self.bottom_thick_field.input_field) - bottom_thick_row.addWidget(bottom_thick_label) - bottom_thick_row.addWidget(self.bottom_thick_field) - bottom_thick_row.addStretch() - plate_layout.addLayout(bottom_thick_row) - - # Depth of Section - depth_row = QHBoxLayout() - depth_label = QLabel("Depth of Section (mm):") - depth_label.setStyleSheet("font-size: 11px; min-width: 200px;") + self.prepare_optimizable_field(self.bottom_thick_field) + row = self.add_plate_row(row, "Bottom Flange Thickness (mm):", self.bottom_thick_field) + self.depth_field = OptimizableField("Depth of Section") - self.style_input_field(self.depth_field.mode_combo) - self.style_input_field(self.depth_field.input_field) - depth_row.addWidget(depth_label) - depth_row.addWidget(self.depth_field) - depth_row.addStretch() - plate_layout.addLayout(depth_row) - - # Web Thickness - web_thick_row = QHBoxLayout() - web_thick_label = QLabel("Web Thickness (mm):") - web_thick_label.setStyleSheet("font-size: 11px; min-width: 200px;") + self.prepare_optimizable_field(self.depth_field) + row = self.add_plate_row(row, "Depth of Section (mm):", self.depth_field) + self.web_thick_field = OptimizableField("Web Thickness") - self.style_input_field(self.web_thick_field.mode_combo) - self.style_input_field(self.web_thick_field.input_field) - web_thick_row.addWidget(web_thick_label) - web_thick_row.addWidget(self.web_thick_field) - web_thick_row.addStretch() - plate_layout.addLayout(web_thick_row) - - self.plate_girder_group.setLayout(plate_layout) - scroll_layout.addWidget(self.plate_girder_group) - - # Spacing - spacing_group = QGroupBox("Spacing") - spacing_group.setStyleSheet(self.get_group_style()) - spacing_layout = QVBoxLayout() - - spacing_row = QHBoxLayout() - spacing_label = QLabel("Spacing (mm):") - spacing_label.setStyleSheet("font-size: 11px; min-width: 200px;") + self.prepare_optimizable_field(self.web_thick_field) + row = self.add_plate_row(row, "Web Thickness (mm):", self.web_thick_field) + self.spacing_input = QLineEdit() self.spacing_input.setPlaceholderText("Enter spacing in mm") self.spacing_input.setValidator(QDoubleValidator(0, 100000, 2)) - self.style_input_field(self.spacing_input) - spacing_row.addWidget(spacing_label) - spacing_row.addWidget(self.spacing_input) - spacing_row.addStretch() - spacing_layout.addLayout(spacing_row) - - spacing_group.setLayout(spacing_layout) - scroll_layout.addWidget(spacing_group) - - scroll_layout.addStretch() - scroll.setWidget(scroll_widget) - main_layout.addWidget(scroll) - - # Connect signals + apply_field_style(self.spacing_input) + self.spacing_label = self.create_label("Spacing (mm):") + self.form_layout.addWidget(self.spacing_label, row, 0, Qt.AlignVCenter) + self.form_layout.addWidget(self.spacing_input, row, 1) + self.type_combo.currentTextChanged.connect(self.on_type_changed) - - # Initialize visibility self.on_type_changed(self.type_combo.currentText()) - + + def create_label(self, text): + label = QLabel(text) + label.setStyleSheet("font-size: 13px; color: #2f2f2f; font-weight: 600;") + return label + + def prepare_optimizable_field(self, field): + field.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) + apply_field_style(field.mode_combo) + apply_field_style(field.input_field) + + def add_plate_row(self, row, text, widget): + label = self.create_label(text) + widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) + self.form_layout.addWidget(label, row, 0, Qt.AlignVCenter) + self.form_layout.addWidget(widget, row, 1) + self.plate_rows.append((label, widget)) + return row + 1 + def on_type_changed(self, text): """Show/hide sections based on diaphragm type""" - if text == "Same as cross-bracing": - self.is_beam_group.setVisible(False) - self.plate_girder_group.setVisible(False) - elif text == "Rolled Beam Section": - self.is_beam_group.setVisible(True) - self.plate_girder_group.setVisible(False) - else: # Plate Girder Section - self.is_beam_group.setVisible(False) - self.plate_girder_group.setVisible(True) - - def style_input_field(self, field): - """Apply consistent styling to input fields""" - field.setMinimumHeight(28) - if isinstance(field, QComboBox): - field.setStyleSheet(""" - QComboBox { - padding: 4px 8px; - padding-right: 30px; - border: 1px solid #c0c0c0; - border-radius: 3px; - background-color: white; - color: #333; - } - QComboBox::drop-down { - subcontrol-origin: padding; - subcontrol-position: center right; - width: 18px; - height: 18px; - border: 1px solid #606060; - border-radius: 9px; - background-color: transparent; - right: 5px; - } - QComboBox::drop-down:hover { - background-color: #e0e0e0; - } - QComboBox::down-arrow { - image: none; - width: 0; - height: 0; - border-left: 3px solid transparent; - border-right: 3px solid transparent; - border-top: 4px solid #606060; - } - QComboBox:focus { - border: 1px solid #4a7ba7; - } - QComboBox QAbstractItemView { - background-color: white; - border: 1px solid #c0c0c0; - selection-background-color: #e8f4ff; - } - """) - elif isinstance(field, QLineEdit): - field.setStyleSheet(""" - QLineEdit { - padding: 4px 8px; - border: 1px solid #c0c0c0; - border-radius: 3px; - background-color: white; - color: #333; - } - QLineEdit:focus { - border: 1px solid #4a7ba7; - } - QLineEdit:disabled { - background-color: #f5f5f5; - color: #999; - } - """) - - def get_group_style(self): - """Return consistent group box styling""" - return """ - QGroupBox { - font-weight: bold; - font-size: 11px; - border: 1px solid #d0d0d0; - border-radius: 5px; - margin-top: 10px; - padding-top: 15px; - background-color: #fafafa; - } - QGroupBox::title { - subcontrol-origin: margin; - subcontrol-position: top left; - padding: 0 5px; - background-color: white; - } - """ + is_same = text == "Same as cross-bracing" + is_rolled = text == "Rolled Beam Section" + + self.is_section_label.setVisible(is_rolled) + self.is_beam_combo.setVisible(is_rolled) + + show_plate = text == "Plate Girder Section" + for label, widget in self.plate_rows: + label.setVisible(show_plate) + widget.setVisible(show_plate) + + if is_same: + self.spacing_input.setEnabled(False) + self.spacing_label.setEnabled(False) + self.spacing_input.clear() + else: + self.spacing_input.setEnabled(True) + self.spacing_label.setEnabled(True) class AdditionalInputsWidget(QWidget): @@ -1587,20 +1208,26 @@ def init_ui(self): self.tabs = QTabWidget() self.tabs.setStyleSheet(""" QTabWidget::pane { - border: 1px solid #d0d0d0; - background: white; + border: 1px solid #d1d1d1; + background: #ffffff; } QTabBar::tab { - background: #f0f0f0; - color: black; - border: 1px solid #d0d0d0; - padding: 8px 16px; - margin-right: 2px; + background: #e9e9e9; + color: #3a3a3a; + border: 1px solid #d1d1d1; + border-bottom-color: #d1d1d1; + padding: 10px 22px; + margin-right: 4px; + border-top-left-radius: 10px; + border-top-right-radius: 10px; } QTabBar::tab:selected { - background: #A2EBB6; - color: black; - border-bottom-color: white; + background: #9ecb3d; + color: #ffffff; + border: 1px solid #7ea82d; + } + QTabBar::tab:hover { + background: #f5f5f5; } """) From 7e8c50926467ee9fbb460ad4ca4575fe3806bf44 Mon Sep 17 00:00:00 2001 From: Garvit S Rathore Date: Tue, 28 Oct 2025 16:30:15 +0530 Subject: [PATCH 20/59] Add requirements.txt --- requirements.txt | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 requirements.txt diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..151f1ec7 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,7 @@ +# Primary GUI dependency +PySide6>=6.5.0 + +# Notes: +# This project mainly uses PySide6 (Qt for Python). +# If you prefer conda, it's recommended to install PySide6 from conda-forge: +# conda install -c conda-forge pyside6 \ No newline at end of file From dfc94fa7f62a380404951250020f7d8856acf3b1 Mon Sep 17 00:00:00 2001 From: Garvit S Rathore Date: Sun, 2 Nov 2025 23:14:08 +0530 Subject: [PATCH 21/59] __pycache__ --- .../additional_inputs.cpython-314.pyc | Bin 0 -> 73823 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/osbridge/__pycache__/additional_inputs.cpython-314.pyc diff --git a/src/osbridge/__pycache__/additional_inputs.cpython-314.pyc b/src/osbridge/__pycache__/additional_inputs.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..228e40b0be9572237daa19a20a16800df664e4f5 GIT binary patch literal 73823 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 P@VTXg((+HE8GYUp=n}kO literal 0 HcmV?d00001 From 60ce5418e5e0a247b1d78b4bba2c4a81c2b5f859 Mon Sep 17 00:00:00 2001 From: Garvit S Rathore Date: Sun, 2 Nov 2025 23:14:25 +0530 Subject: [PATCH 22/59] __pycache__ --- src/osbridge/__pycache__/backend.cpython-314.pyc | Bin 0 -> 4527 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/osbridge/__pycache__/backend.cpython-314.pyc diff --git a/src/osbridge/__pycache__/backend.cpython-314.pyc b/src/osbridge/__pycache__/backend.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9910af000330c9c6f01d57598f266556edce6894 GIT binary patch literal 4527 zcma)9&2QVt6{kK;%cA5D#g0Ex(}~v`C5^GMy}L>FQ962!l8!zn?#ay$){r^G{o@#TmQ{gcEQVv1n=BLovz z@LM{%j|pBNlA-_LX3~dzmnznPC4F||_-m-}fb|Ul7KHA97|;X5gaXKV5F_d#CIB+b z1VQ#NA&?{!2HDH>fQ&FC$UdeQWR!`3q?kTEwnpf2CJOUYD89&79*MG0)D~4qsERvN zb_czosPrwVy7`3PrmZnLBWhArt~l&4NxYr_L6FDSpz@Spai3Y%2-($R2x!?WCkV3z zHdbMLz{7vDkPM=MJK6hOCRbYFZsrOr`BXWJ!dr^asELRY%&9p|=kA>HK-I+QJ>`lPUg2nH16foHfS1>CiI&)ZIHCu4~-KH zFl%5CJHZoCAZe{({G(V+fR`$h>+6El*Fpl1&$$Slks0lR5N#o_ z3$Fb~ZmqSt>rNlzMvlEsstY#NhJC}aWEy?wUPH#wJP0-9+MBfzY)m)*x6D=t88efwHURTte{*2OC zuZgW5L@x8Xs7ib-IfWwlGFUEVR`Xf&SVVp0`zu+y2Zi~19d0d@Ft3YpuAI+ur82Xc zF0V3K6fsrJUNnet+#wE+pb__?@eEL=VY;}ySX?aLLub}f`PFR6Yd+XE=?YW4olTcf z0z=vjE?-Qi%DG|zjk*(c42{^>9Rux4TFMrevt=fiMx?onl~e&yE@lZgqt;rp_sS+? zJeW=~OfI#Qy_>qv-OXjnx6qht(>WH!V>gS%@=6Lc+wNeeD{eD&bz<0}bSHb4OBI&# zSv2ILw|BNVX&J6^V3fCh>Qau$WEm89t?Z88iXzKmMHkjB7HOtfDshWUDxE7Vp{sz+f}%=YmlRp!aCjoWeigns_~zkTfbT>2K7#LK_^!cs9R>8O zA7F76i#aUju~^XXwwa+%7VCwirq{y~oaogDvRu}Mz84HNO9X5!-BrRTzn%)zWdedSd-jM2^W z*Y`hb-sTKmG^!1QdhFG)VP^Is&oeMr%%F;1mBZNi=JaR#-!}_@@yOWvy+M8F)p;j2 z!_TNcefPZlV!=q2465wKIE-CrzW2pnCSK~s>V4zEmO;s#Dr3!wD|_GXD_E&wP(p`B z{9JQ#?x+00^vj@8_|}N=yMdPD(oK>Es{M)PnU>>XUwKsyzDYrL5_DOXLfoRJ;qrYPR?*zxHD=Wui{o+-?k1}oSMtZlOH>C#r{e|)0Hi0M-(7~ zg>x9u-eB}Stf?pP9ii>kBc#+uYDNaON zR~O?4-v)LDmX*pQx~5dCqDpIUjn#J&dACJ3>h#4e&`3gVjzV=+lJ%tDI#vRY#BB{v zJ2PV)0dX+r6iwi(795)NuEnEp8;muavOgsbNy>;#|4hEuxth~sV$kHRG<1B^TBnZb zWrtmGqsCWH(4#D6=ZHGzC3j-D0IbwISl}bC((YXkA3GKzS`lmg?Qj=@Nm@V zI|8;=S?}=p|XaVxZX?#RGfgSy;$JqQX$!o z-<$Y#2k*@Y$JsAB4n;ZccMZN~kHk4{LsB)pCILYORC_p1P%0d!;wwePZzDB}MGlK) zEJ|41#o_@L4?&)=A+@)-P?fA=X*mWehsere1pdX ImL}-`0IT~HsQ>@~ literal 0 HcmV?d00001 From 9b20c439c19061bcce84208136039ee1a56f1d8d Mon Sep 17 00:00:00 2001 From: Garvit S Rathore Date: Sun, 2 Nov 2025 23:14:38 +0530 Subject: [PATCH 23/59] __pycache__ --- src/osbridge/__pycache__/common.cpython-314.pyc | Bin 0 -> 9272 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/osbridge/__pycache__/common.cpython-314.pyc diff --git a/src/osbridge/__pycache__/common.cpython-314.pyc b/src/osbridge/__pycache__/common.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..db6786be295472edd4074b189c35d989ba105c2e GIT binary patch literal 9272 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 literal 0 HcmV?d00001 From 2c80cdff0981deb0d9679e5bfa30c218a05034c6 Mon Sep 17 00:00:00 2001 From: Garvit S Rathore Date: Thu, 6 Nov 2025 23:32:51 +0530 Subject: [PATCH 24/59] Redesign additional inputs and add output dock to the template page --- .../additional_inputs.cpython-311.pyc | Bin 78683 -> 88970 bytes .../additional_inputs.cpython-313.pyc | Bin 0 -> 82536 bytes .../__pycache__/backend.cpython-313.pyc | Bin 4798 -> 4470 bytes .../__pycache__/common.cpython-313.pyc | Bin 2137 -> 8602 bytes .../__pycache__/input_dock.cpython-311.pyc | Bin 46930 -> 48967 bytes .../__pycache__/input_dock.cpython-313.pyc | Bin 16236 -> 45007 bytes .../__pycache__/input_dock.cpython-314.pyc | Bin 0 -> 43711 bytes src/osbridge/additional_inputs.py | 613 ++++++++++++------ src/osbridge/input_dock.py | 229 +++++-- src/osbridge/template_page.py | 387 ++++++++++- 10 files changed, 946 insertions(+), 283 deletions(-) create mode 100644 src/osbridge/__pycache__/additional_inputs.cpython-313.pyc create mode 100644 src/osbridge/__pycache__/input_dock.cpython-314.pyc diff --git a/src/osbridge/__pycache__/additional_inputs.cpython-311.pyc b/src/osbridge/__pycache__/additional_inputs.cpython-311.pyc index f8fb34e663aaaa760ce2afcc7d28532c70f2cb17..d949c1ee24c2f9b993a7e494ea2f58f1ce77d974 100644 GIT binary patch delta 17864 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 delta 11645 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 E0hUbe&;S4c diff --git a/src/osbridge/__pycache__/additional_inputs.cpython-313.pyc b/src/osbridge/__pycache__/additional_inputs.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1509dce9d5e6703db5f1faff64cc6a6220f64cee GIT binary patch literal 82536 zcmeFad3;>QeJ9vB8h8L2H%Ndu9}W_1l4#slgBJ)8JVb&d8qft<=Olz0<(M?0o>yc0QgCdyxn=We=@UM3$Uqt9fuGrPl(%E-&j z?C$qh@6|gR)c{Dzi8E1wtbVUv9lyGNSN-Z>%?sxyvHCLFP;`k5r!ktrpxc795 z!|@e|&!IScDZbQ4Qj`?-o2sPZH|>$Mv*}9uS*PMWo1tW!%~UeaN{VzgOUXK$tz@6g zQF6|@6xZ2YCHHKel6N*=$v<156r3$o3eOfPMQ4kZ;%Ta2}9CZ$#tK8ws zeJZ^(O{;}c$3pWEnr{iMXQ2fMEwqF-u+Soe7F$C1vd|KQ?y`jLW1+hdT51VxWT9mU z-D3&e&qB))T44!w&!)O7!{O}y>FK%9+|r_dUOuyUWjPd-ADf$=4TR*GB}G0xH+%W2 z|3&$PLSgb?AUHR>n0--MdVX#?5R|6_A^+TbU|OD|l=946V17Et5-#``myzS7mhOsQ z@h=2I0R<^$<`!9b+3!;m4ZDltnU}N@<2jceJ+bt{Isc1G%OSj+Hea$Yjru3`SQ(ej z`6mPO@vKYd<`x4d0X-fEmKG+L5Fd}Hl%?e>^q6yLXh~Txvb(q(ynJFg6k1xu7vGe! zG(X?31pJiva$xFNEyejjFzBBRXfM9GaNy$7{M^)wl;VniDxjPR1s39YmwX}r)U$zU z1EL|SA0nMXeYFDJsqxfHp?HQ54d$Q6TfwElrRB-_z@z^8xoLlBNr~rPIw7&TSo$+Wo1zgT@J{;M^DKMe&yNaE3DCjp%f_n2hg>lBXw;(b@KCbfvc$T zk-8SSMQ(4ADQsqLe*Q?^;?iQEPJUrweld8Y?s6z}#oOF`_3Bm6)ppO4GTYqN($dn5 zjO&gbzTyvEmZy)@op0^%boR8j%dOp>p64 zdb)b?+v(}-?rCk4+d4emt({$Rho`NpvkUL-o(_B%X!9TzeQ)!$b+mNJt)AXi`UUje zJ)IqRZ|&&mz;CaowY9BtpvBYP(?%JhmL2WAU1+yf6xG?*N>A;mL^To98N)&5Xrs*1HAoEOXv(yl~#H95~+ZI_$=<;8oLy z4X-*MR=jEWu;XpVhb3=SykpGjjZH648q+J2N!P!NF4yE>V z_TsnI(}__!fYMuAd(k1io{qLw#6t@s`z}vU8@fq<)0#*7LX)KzgIiCBHqj=7o^Pk8 zx1+14XFxBu-P409qD^216M9yghd0lz#ye)X?ysa=yG|*T>8c@^Q=5>aauA^@{lnPp8 z@!t_29PmCh77QrCr%w5m=jTFC4PqUgU-C}}pSlp#Ho>QY%G6U!L2YMy%5UtR6KoF* zdak?}--YdSg0I38C}d)8YH3laMhV6EA9TTc*>NW$FRSoQT7fe|sYHNn>8E9%E&acY zhp#xsX)9o^Q~w>IuL;hV(y7N=bd0C-k0Z2uB)B@qn1jb1$x8T$S4&Pny~a}V9KN*n zv<8PeJ$%Hn{Y)(_EG#X`Y}*N54(K=ouXqsK4fX`SPf-(i!p`N;OjD2lKcqSw*|P48 z&0BuS2%}F|sBsr(y>i!;7vzpMc=XWPapi?RTfE67WjdgEnD5YPZX_5 z@lVe!2fcF7rfjhL%mQ~Ne(p?VOqcemB z&DE0Fn7k1G}QFXlDIaDm6 zQnuBgb<==II`y;?-A=ar#xnwoQ%lo~op;(BUD6## zqf^?vI~arkE%IN{$bu7=bvbw$Lw-7OU7iQEBnLn)1wpQ{WjUzzu139qPRO*a%AC5| zvL80BayuP+Y|Z`|UyOMRd<=`Rk<~|EjE$_O=YoC^l+y_vB$%r+E%ay3LRyLW4DFqy zKl7V5Ul>lLd4!x;M*Td75se~!)YFK66#s*n@PO2$m$>v1Yv3&SKm(QGGk=ALuK@cP zv3^AuITP!U9peUmGiJeMI%y8VQK97gh$sZv1 zTwa_gcQ97c!ruNXSLR=2)69S63U*Cp&L0YBb5ag1q4}|RU`KxeY!v_Ev`o#a%}&qz zb;y0#of$6(214h-E?HP!IL*lQc=lXyZZR10FM<_8HH_yXijT;CAK_nY4N@e0P^gK( zQpmt|gS9G1&8@Fpg@9-N#RYP}`3-Oeh$KJ@yTSk^u@Yv1P2chVf$jcf_?|4#sd z;WL*^R*YrripFin4F1fq$rl&G1$9hsx{dx4qZkW|9St+~kT&cepqQ*%a47IXs0qB8 zS)eZSff)%+meBEFx^p2Daw9G{r?4kPGkOy;K?;LJLRE32gW zQk8UH=9r@c9NKcnGtx6Diqn@44rzwZ2|v@9p-5wnBPmK2hzTiPaN$a5ZecD=%h(Vx z58wZ1$TU3qn8tw&UG|4$|NK0dSh9gy$diH4)j(iT*3$;2n+KMIp(SL4$Nu?wc>%k) zPJCGSl2aUTstM6V^nUNZ!ate<3X4LS8%-N?dyJhp#3isWh)4^@ z%O34NH+IrDapB_Vne%6!>>oXIVR+*Fg~5~Yd~=t_x)-{v?dgW-L7_1n&pP?S6`%uVdm%8*cKLV?Ti~?4oI!GOk)ed_`B5MXSXkp(076?)<3&@;3Q@zO*z5)_`xj>e z)A39Y?u(!V;`vL96B_LLmmEuMcX~V>Yw%1wd!D2(6SQ5%Gc?*do;5KsHSZ4wCnkcl zt^vDznPH4dH^IIqah3P**8O#S4vqmW_{qzT+fwc;nW|KFV|P@NZ||;MX^8A@yq5l} zIUADeOF6IP#H4+yv~T6P)!NldYbjBw%?PhnrRp2cMWxygG8`2RuN_39eEOLGN`6d| zRY{K4_C_VI5!9$kjjL%<>5vh!SC#g@@oa2gpSrK_d*iVq!|IXYsB{7ORqlPQ?^=3P zDns0uR0jBBQoSnGubhlZ2et_#c;9#_)_7QLJp8?9V@EHjM=wOBi|_LKRH;(cjp?r~ zeRU}+c?cF(OD!~3?NO^eQK|V(h9fIitI7dYIuMiERH<#PBi4ROZ9f&2PBRz?A-gg9 zwTZ7z#HxDKs-8%d_mTT6qgfQYCuB2PwjoSG zdjj@0Vr-Y@M?MAFhGzyFPa-7Vy5Y^HCn?L=gpI7gtTZQjl2pc4DNZ$;?skTow8_-0 z(^*y_r%XJ7X_=UxoJRt_L6(7%#M6L?42=%E)0Gn_K^cG-cTUX*z)Bj#^P8#4Sqe(0 zX=LRrD5oiy<2D9fT0lrXt8d&Jntr?Opm&n662-cay4lK3PZ=A7jtP4i* z^kxccb}KcMMVuU#CSJa2oie_?g~UNOJhXQ~$#3TWdVZ|npjvP+QqZ&-iZvfon~%ks z2i4}mNb}Ho{;7586l*JYnpSoXK|%1mnKJCorrl)`^5Th!c=iOXxXbhO?3$Q(4l;B7 zOa25_btM=gx^Qs`pED;WrkAF$Bhi}5hKcexp5nW(=!X2Exv2?%D5T6yE{6hGhvT{W zk~|fdpP!gO#m^%$tN3wkX>O%#$Rm_HNT}9NzUcU~^rt^eFLfTi+a@_ZAC{y#Pp8~* zkk6{aqE8{F;i&J)-TQa&M8v>T&N|0a8GJf|04TQ+2NEL=vKVoY?aKg(ki%3gTuj9x zm#J9fF%^q^Uk<(&kb;F$=*xv)6d^_?12=NkEZfk5bLZks6Gtig=+W;W-spg0h10B2*6E|1RnkuJbKV zHii6?OvtXqk$EJYc{XJo#kN_IKwV%NW7LIZiF&+X-dLm`mrbg|mjaB|$QLt3Kojs; z0j`F_ojN?^&QQ7lvbOQ&%`7d2NHDJt@WLr9KXc$)L#1N^i~-;p`O8`qY#U*zktQ&VA~?%=H6LdM|%WHU^hk&`IY zqe9HKpy5vw)2*W;S#0!7fGcUmNkE|xhjGIA7_Ut>0gNbrO2dB_8~*P~g)yl{m1^!d z3Ts_|>nP01)nrwPKUw27YQUTp$}%{}fMa|&V4GGVv$meGfc8Dkm3-b(ecKTB_AY&#As zIMjR`V#_xeDcJG_EHEL=*(S_LWw6G0?idC7Bcs1+&2au?`(DpO&0pL1y=FLnvVCvl zq2{mcJ7LlZ&hkA&*xSOku+&;SMx4GJlVymOIgn5+UPY`Y5fcfiTQ;Y*K;Rf@7kt-) z7=q>+exFbR#&-&T)3)d6I?YHZM(v|-Fm;{AUP zkl%lYyb^ff(gX}Rdk`ok^k2h2wpWO0>F%gLN!2uHx8il$tm#2tjSZ6|I-6^0mCv+` zBJh0@Vj6pDv;N^n?e)@y6EE398Kj0j1()CmHfJ+me;f;(EfZ78+3N+jPlT56*4TLu zGxSQ_c!H5N(@B5!reJlm%j-#eM*lHvHY-w@RiunTj&%g0(>B+N)WR4o26d!OP$zFn zFZit?QG428+Gdz+k-f&(vWXX|H^~WxO>ay8P;0^rqNsY83FZNPeE^cfQjuh3X}#N& zyvw$zEz`F1);?IJiSkLLeatf6C$!65t`GsQ^pfW#kB}OMb1NhV(M0_I7c3%v7sLVm zB!*r9-Ky-ONIC3%0Pg`sAv{JoNx|9R>vPEi;m^Ig-tCjE5QK!RH}cP z$D%c%9SRAu4|!Tym*=LQUBtpIg)`7tfp4b|B(ufMsXC>tdT+{+)>~;`v%Lk-j1_fwae^1!%FE zA`6X9IE_e?wHh|N)2}$8T(uxI5M(w@8mhMN@J2mP=P4M+wDCKD^ExEbm&Rls^ng+Tb*31 zTbqnZy(~KtQM_^Do5Np+7^59xj6F5$HNBBC@3q_wX;)0DQ>D5$+P-=8>qnU!1tFB+ zjziju@+FjC#L6ex$x78)%3E#UIr?oxJ@GEpiU}wdqf(PuA0;Z)k?^JNHTN}$UI02t zRPH#^85hFd`9GAlP9!?S1W#O^l=E zS55g<1HKa}kcz}4YD`Fr3>gUVkw{KMv7=HWd!}q58)DBSk%>zC*)z3xRND70fx|i_ z^tI1__4AOpn3Y^wg;c4PNnm19g(_8OfXAd}Rcc;KkF^Y|EhHz>V^DM5lIk`Sw=`vG z-rw^;)@UVeIntY)v^CL7iw=Up?qWnM<(}X)LIeaaEk1BS`nM%+#|_ffBxsWorUo&@ zau$%hV-~V`+#p$v`V!GusKI9SeFmo(`KFJMvV4NGiFVRQXn9Of!a*7BQ5F94KZ4jlr<{>q1=9p)>N@ybzFZL3 zI%H-^N%9g9dgJvx^YI4$#Pi|5Xg9}5BmZjtic7Jj64hwGnFD`%7%c!xKjrZ_B zei9Ot+xC;ev_P#r9B%Vh*iR%+Va}KDE8wG$!@5PQ3i7w55_})od8@W@sLWqW?&%_y z)tJ*)=qnuW;3-B(s*vC;tBW`f;rK?rnSA`R`Dx4tGyAa&#LEB3r3+dO`5Lna3^8-f zE8;D(m^qfY?aa@7sF`mtb1*Z9Ve|6=ja+!+*CJ zqk>XwsRZ92V2mE-5ShQ0+{NPyEB(hXMg?%QJw#t)(ITlM$4sdB)0kCnEOn%xhnv5A z&1Y?T!VKq6BZS2#IH+Xo;_C)jM!Vgm;l2Te$um{rb4(9OCQ_OmuF|k#8E0Hb|A;9P z6UHsm;UQAuxnC|I)^r;GecGIqFuePjXV}Sa@BJ1lxk|eI0ve4Ib@e#>+xI@LLdr|< z-lz5o7oTEyx~$VB5X!r~_p7YA5)o|6LU{oRXJ0D${ZoG#{=%_Yyuh6~9?l|=>@%}bZQ-^&8WP;rVPmG4-LoT8Ct^)yX!3k7$HX%$}E#! zsY?`MbHz(Qhn-0->qP%2CP%xSk`P zr}fZ;-a~Ps?u;Yf3T-j*OazfWRy+-ddg8kWPY(5uog1Avb!KGnL z%JLd{rzp`LD1_Lvo|w|LwsN5^ur$L7CZ&v`mm#_>n8(%#%q&J=o`Ya2p35jUK7Qi6 zI5>kR2OgQY@aV~r)BVGzSbnyMJU^yvrZu)kpXx30GC`36f_2nC>6SDaE>|PCIMv2q zwNQ|XB$)$=M`!)hXK}DTUXoB_?OOpVrhN$Nv5W1Xv1mc+4o1`&bluMpOafW1LClgJ z0wQBUZ8(AlF}@+oEvtDgOQ)iUW?FAJ9+moUgSt5%DQnX|>{q4zt3|6LYej1#QK^TK zT#S|~ib@TjlS->@JVDBB8kJb9O10~Cqfu#$MPy`94z$w{v7q8tK6@>lkt-Byr7kM% zHHey()TmU?-WWyZUjZex=^dHK`_}jMMQRSOlW@HX%FbwfVgBk=owIEZrWPShH z)yub}?k%X`zsviC3dYjoV4~Mgs9+(LunjIuRB#@rA}n~KV?==WZb!9RsDON)*T+Ox z8niaY1wsECB6`4OP~bEkG$*3F#|g_~j>E@rN^xBUd0JA5F=?kS&zCoD2uDRC(TAk= z@^(b+<@pLea_NG4qgG;SuaLLM$3*Rk`guG4K_l-|^S9aIE4pNG_qOO`0cFc;WTR?} zdFY}vCT&aAN)JWV5`xJb#!Ete;6|dS1U#Xw*Ibhu(t3@yQ-9tJ1F z{AA{CS=o2<(8cVr>}}gO`=Q#mn!^CN1pJJx+m~^6A7oDxl)&qV-Vl%LEMLLLFslST zhJ3f@>SZI=M_=a!^+vwpSyjqgWHDzfXWL%Sd8l4DICM$ZKVkwJzhKi>@)( zw$QpB3R;;10%(u(XY+UKF*At-YQ)LVUCYm%!6Klkgd@9FJ#X{p{xNBj-1Vfu~z(;ptYntHYfZo-Si!8XR3> zT`@i`SBQX$n{GTasJx2Il`oS=Y+L0k+ojr-{Cm^`QsWRHS0VSEO0Jl-mCl;!ozu#n%Kz~N%O+%PK&1x!^~1RWAw!<0l8oQ zJrS04rl>buJft5qv2bCvLL4Q>!zIR710vZQc9L~XZ+N%QKNASOC_fTVCM~J|nt=!) z!xh#DW(NBg!0CNI9ns?H1n;oBQ6UbrrdvbdNm;wf*XZjCd0!{b;MOX?M6cf4Ccv8cz={F>1-0P!BCLLK6$jrpC}? zm0BxmOrj?g*ETJ`;tpHBfl`OlqV}o^NOkK&u}z699c`d?6tROJj@`Qx2C2^O?Tl1+Jv7`uV)6tL-0#WzR>BdvZC$F` zkg8%5v5wadJrb49vDYS5YFeEFyZd&{{?}gON9nj~Q>><4t!a-+9Y*bUQX!oWyk2^U z^t9eO@tt$u2E)AH!X7@TYFu-a`!PuxqTX}wOs)GyBkr-N^r)>Nb}CV*2Z#N5q`rTh zG{a<#-@o2WidyWoMU`6CAW`VNU3c&sFcuJ%YFIJE+Kx#i4v97$xjFTA=R38LCK3?X zfap?dx}s9I(T)!!G-zHcTJ`Oas)4WYrHr5!zLsb?)^4dRMUwBzo|8>e2KxR$*k z6=^*7^;(iLu~*{yub?Lz6C?(F0_j4J_v-eqyZa+`C)TNUs27cobzG|XO^1{-W3 zblk2z@Qn&;1Wj{}veCv?^w8nxk&*S$$JU>CGID4^ zCL|*3M}U}4EGIZi?qZ7G2@29>$}&WUu^v*&;3BRr=xX3pj^&5JDJgvhqZX6balp^2c32WTpyEENHkKc+(H3wYGk{SQs^ss z0J*221YSps2XWhH`iee=wilF)e3Q@8k6ThDYI~W_jU>F!?b^Nsx_QK7fU~q>vi0{k zi)iyKsl;s$-r&d3_JWd$=ZQbd3JI;o0UL1aC4mDb&`{*d4_i-$Hs>%R;F^I0w%fu1 zD|MHJD=i$b0Xlyq<3ECmBu&;iZqV2Nhr?)Pw(%I?-2I5uO$A!45E@D6_25VxH6N>m9;8=L2Sg9lQxX_jNRV1z{z#9kwv45ZgBDK88{)L^&BB}g(6$6zG%$`tD$vpoF)&PCO9xuI z$!KZL%Shz4>~sWJ&h$ay)|fgt1!$+!KeIYsd55 z3==X*&*B-U^^)SUPG2_uGI7?+qznyOjbk#P-{?jj=71R*`b<0wCPEA|EoRubaZYaw zxu_^Fg95PRWzc2g1_jP>MgGh1t-WR%{v?LjmP!PLfPNow^E}O8@$@sZBPkk?GfpGJ=GC-WCLyK#aMCy@%>Md95RoP9*I3_#fW{j$x6 zj5Lc6OicYg9sgyr>hJ>{FCvpHreHf<$4k`h3&18vNPZD_PT?H?%-rlUZalf)n)IMi zZ*Q-DmWkOi^U8BL)H65L$4CT*)mtRvjyuPg99>8IrQ`8Tz3^do&BLPcbp9ls%MB_d zN2QGuHzWOL&f(;bE(5+ojd+c`Q{*vpI{h+k;f&|;JhcmiGzl_KeEQ^>Q>RDc`E+9k z#HmBDS%90mrsJhBc}mw%1}5yOg?M*{hIbnU>6o|iz6tks@l59yXO@B)09eDGHPrbF zc#k(EMV)zCj4W>|kmjENa|Y79N46PDz_AKKnzuKFU>uwQwvrrahJ6KgLZ<>JO%U^O^d;A86T*2sR?m>y#2@kc-K zaY}7AtVL_N5y}mW=)EmY{XCE?j^vRsi3w!}V;vXOj*C(05`$hl{bQUJ;Z0ov>kG_S z1@#b!8ar_(o?}*7Bu3?FiW88_Wn9Ewou<{}L8WzdV(sGf*jDzV5kM^=@ z)yZOUgiFpBDGztjC5I){;iMzmKSv9WLtAiu##QWqIqs@huQ(hjIdUy)gBwd;mwi!b zlw*!BuGDI{Bhb^GYoQDaF9AtMOOh1p9yQNb9I!r*7tLAk#e?x&v>`cu_|OkYE~s-# zpWsCIHDG^D^@4)3(58+NB7zd!j?)^pTFeY=`@X@$F~5rp8{3SvCZ34$8>J?H*i*)V$_H`6Fbn{Lk=)VaI?d#CYaqe~|88&_0n+P5EQ;7>!oIv*0q8 zczR%AE~FcrXBpURZSP97ho{|5(YSf!3nTtZ8uyn`IxebU*7e`ZDZ1XVzQ-NQIiTho zV7GeIsIHor>wxMyuzK|7qw2wdsB4gYs8?O}F_%Ymc~(F7_HLpWT-yCII_}P}c``Pj zS&&;fitGP2?`OvNdIPY0#ZgUT1^^{G*gSKL=p)Sf&6nc6iYsZJdyuil$AK|Y>!Dvk zLL26GK4y77e9T_J7>12CF`Q}M#{nm5;7h+yoFtCZh?BwcN_)I$EUn4ua5UutbEg)_ z=1`iyq8Y&QBRc>0N@&}B_-?DByHgWCptVB1Wy;~MTP?QRWPN3`W* z6i;#I4DOr{DgPUV-X`xqk@uhB@!d3+EVKYjO`6G)#&Z{!7YsX^K`k{UOk}{s%jjMk zp1~w6tIP)U;{XDIX0o`e7#qPBw!_{@FU>lzQ7OkN+tkXoYv*APv7jWDU#I5Rt;|I7 z4{Z#cjSW4a4m}YYdRiTNIyUrab?DP?>{S$*7p~{N3y=!c+BUVI4KKy=x=a4aozxVxO>t?g*sT`3f8=uC57LDD`N6}3#~eRA zmUl8gZ3>NOxJ*2;-T4b>-()u`8LI$N&Ze*xmeM3RzGkp>W{0~rPh*tuajm4zabtyI zE2^>Tm_R?&%JG2tSXK^W%~?!)yw(eyjcKgQkGIm3zmlzP#@b?mQDl?SBIBA(8B6u0 zdB2Q2p2Qxsi>EYY9>9yz^*sPBGM3r|yoN)?e|=681CV_XUpj*ax`Mt8$f%XF> z-y`o^vTk2y#MlUtg>w76s#y2>`KNChzt95OkL zU;WFJ-`>p#n7};o4`@*=K@sd;wpdOL{%yF5?xYuG6|x1gSFP-gRQ6pvztMLr);Fy7 z4afR?YM(FGH>UQDeK~MrFjm>DRyM~fJJiaKNM%=acXxFBxof9b{PSwx`B>kG+BXvG z8&&&8zr1{7G*;EDRyD`4gjID!s=A`3-O(qPuAQbexHOXAxY`prIJkl3xS&ogsEZXG zPzw&MwnYm(8wI;roEwF|^3v@p`Hfwxxi?d8p8l>&-9PwFrrLNRQhPB{b!mNcOsyKb zp1o0AwNm)%v5kYx*9W5|`!`C;VkP_3l6|ofk6PmSR&dSxd!LV%;CsvU!CNK!?_@ek z>hCzgNdL(lS4vjlpScR|2WjqndB8c?=lDT+e>Hx7*q1tZ%)b5+*2dO9{fa^%?#NfC z>1Bt8Uw+K1AKBIAW%zO0-GWORa~-ROwpP^PyRo*72-c%I%vM}dlRB2>OUnngh!uc- zo$XGpO$hlKX19o1;p;KQn9Znlu$h@*;4Q}Uhj(3|T=x-nuCTMPTrb?7qdy4*sa@;OgLd+u4n3&+65+;OfG9 z-|YJ3<@M)ZT%QfEhd;Mo`S}mhQug$xzFSt!X5xOeVE?Ux19x^g3L1WLXLm~Ou0Ja% zxqq(+V7=O3cS3S}zpDWLA4#bvT%XJijQNM09Y;6M4myHqSjXIMa_r)h!?ug*r>ptS zA@5hpFff%pXF!1K<%O5CQ~eVhPh&t_4|ocBqzBK3 zqkR{g=C$uYGE;p{hOk001Y1Zr!O<#KyiU` zN;Z@;eU|;{TU)xmwPl3`Tid4X%RYhaJD;wq&8P6NvGr*?TQI!W4AOT)V`uY+u({Fw zDs7%&4G9gWdD>@&+%9b^TSbjEB(#vGvN@?4ipr)u!%i7a`r8zla8gdY=bNaeHr01g zCWM$OH0k3xi-D`$K0qSvuDpqilB@wj1FwHaYrs7uvTb<9*zoH1#p*iLx{g>~uUgj| zsq4F*{};t|w`&_#CL*<+*UxU0)~@uw`Z?RQKfh5qKzrHA6zpa1Htdfz^s5d1k%ocm z=QjpU#RkUIfw9=Ylj^{ev4N-5fu~+yUKx$;?N;}8$Mzmp_a2VyJsPb!7M;9$oe*RpJ>UL}+Bon|_WIbPf10Q6d+bKeMsxd(^k~_kjj~E?vTE7ESXsMT*1q=A z+wJRJr=n$NHkvzbq~9t#bSKMEw(m|UA)J|PN8P9Is0`tJwLg2H*YW-86Q%h1L2v57 z5&M=(D+pV6PUB(QbqBK#^N!*tI6hagS%HMY9i%O6^WofI9VgO}G|{-lU8Jen=%SrL zc?=%Au2;u&eu(!Zm<}&P*KF7A0G9yBAm~_Y$1^T(gX(Iy<=S^A4Q;iVdu#9??jUJ$ z?(J4hU@TYQ5n67o5MuIM<-?aNTC~SWiHJG2FxT#AVFu1^rpv>%;O(}BniigfWycxr z(XBHRZL3|Lt9RW(AP@@5xUGrq>XFZk49K0pt)E+-Qv!5TPtaoKY`2C2X<^ zHLRE;&&CoHu8|jHe^AC*4n5a_c|S0E+z{H~Y4>yifzB{lgBsQQ1GEHNb>h;L0>v&E zqt*oqaVIOrCQ>kUy?B*$Zl6G$W~hbPqwb->w2V?;cHBwN1T9OrGx7Ig`HgCRV=UjT z=DTl~;X3&}hhvS>w9q`2vJNlmO||Bp)cua>MgpWG<- zM*GgMdxzJD7uL%b|28dU*Oe3u%MuR}jt72nCtD{R?-5b;^ZkeVTOHqTP3`ZrPvB2R ztggx@>zZ@2rU)o+KVo%mwR}22SD4BAh~=R?8=c%UM{J9D#0D{Bdp+HrHcvaVV8q`# z*bx4|Xv~_3@PFY$SaD;|!_s#a0r3p2OzZGf{)RqmHB|L#PiScyb~IG?RNn|CP3~#e1Tao=CAL=}`ULdfDe`sP?DQP;E{as)`4-vEjA<)Q4xEv7l-eb!4(Ww9_@U z)VIkxlr~7~^$*s}HjUFpjMM$yofxj*T8Nj}R!>JO8=ik+AD-{nhNp4>y=onl@p5CK zwAu+`Yj6C}W>#VH*v+Ehx>wEL8_PeS<{yaVA5!xV=__l8THO(=?p3RMBh`J8vcvky z+7c;lu?^SL8|6o$M}6x@M%PDQS}*$y4cE`Hm9>?IYm>RMZXT}bs5n4!-1|S^B}0d{ z+O>_{A9Ee366KfRc9MF!BWY@yPzes@B!tlGR^@QqLPOUW!q$9I2{g^jfsMuYJ}|fR zi|a66Lzkh=2j+nhHVo?hmr#thsQocLy+K|g=JpB#MVMQn{NBjeOGAQk(V58AYEE@5 zr(VsekLB!DbM~%I$2=!h&&inQtm-)%^PE#X=PdI3gQ^SssMw)n>Y-z?Lxbv}!N{SZ zTdq@BuA0xMtmo|2kUlHrOvn}M^k?vZPEYZr^x?@5DTKIbq1r*hZ=jdsMn0mhX7q8| zSPGD@^!8K>mF%3wpF1Pm@fdi2^4yF(GaqA6GFL z{o+oIOGA46aJG*Uj8Fn5bq(7kvNAJTH;l6a7Xu0uFG7Lo@TX;VJd@`ilyTIPsb~TR z)1t^ri#jti$hw|NCqCyg4CNVMh)}j(AZ)bXr7_J8jWV=ua4zGPMVw2ut|&?z4IXda zymV}NVD}gel~YKGq2elAuk4Drx^9lfj$BlaTwEW0GIHd}+pf}>Oa75dUMX2`JhM?; zbKL{Xz81Te!%=u56-z_z0PSQ)$5Yl_U2JH$cWXlFzd*jqpOd#i9+O7@J9=USE*tu9 z)9d%i`}gFrNia)Ke?;DIllL-ttK=~Z;6KsRpOW_{?6tTD=3`5`?Wq9}xRDDCt_6PR_6@@abVlQ#*Ek!Qy>8PPNalJ{Nu@FjRZ`J&^` z(hvP5jvP$A`qFxO$%pAqXX)L%RA=p7so0r&_rPR|v;OYsv_5Cg-EhitDb9|&GigI9 z&ZBoPrtWuo?~bNaIQvuXdMliV?|LO?``wZ~&I5P5N}Yvw+wz>d?#j&Dji;70XXD-M zEPU8)dTGwnDIaF1JJGog@>$O^2&Vv?;b_*AyLTAg)0q2DK{D=x49=ILr1?^nbP^DD zKwwesct(0AMREGl%h85DC;Uubh5}yBkrXAX!QqzTCBDE^Xl`loqOydE6q*YJNBxs? zLntDA#J4=z1hqWO9Yxlo$!0uld7Z)rzO*1O`k$Yh^;1T&rXc8&|NC+^%Nhh7tmYd(<(X z&Fg~kP{Ncm;$l@xe;1Xw#(^Ah| zN2)Jp+&ILFH`jQ%^S{z9FAqI1dEyEUwLJ`mZuss zYQ&7ggU6TXaH!N6?Jyr-pZFc>8OGHiXq9Z?2ri z!hu%ixV9xL;l-NP%P`Y~ZEuZ{RcDwvUd$2qr7#c6#i!?QIvhC;nbsx&j6BE>iGw(X zFP)~!NRy>v`lGd-lfc$2NM%_UW{YNv+{NLRGmGGyfproN$m~q$v~}n4FHY0J(hxX% z@)an~&j&qW;az$yGx+OMuA@IO8A9%w>zR=vvOaOOp^8*Pm1U;ZE1%pi| z6zwWQBkt~#=!(HP|CP&%e|Eu$AJ5g{9QHqdBD6T_|DAM)V^fVUU0e=cKEd{G4!Ra! z(2%T!Hq|G|)%6saH#Z$r-c&NnZHewI_sW*b>nB_2Z>#7;`&pCD`p9z9Zt~8|DGe%=2GJ8tE&-QY*g={H z0d$_kgON6L3hm>{N%E+gv}%Vg zOg@9shq3P|r1BJZ25GB_>ndmZ!Vl6hUTka=mOyQv(6%^*><70AO$I9v7}%Osg=0YSa7*cxD}lx7cs5<@stx;iK0Plhw2hxxoDRGI zMF2(u{tD%o$p}Qh63^2gU^R@c4vpvP7{tU3ZSwIF{yMjaosUP7^!^LRQ|97M?MhP@ z7|En}KZR8#7*CrFEuy!z9wX5mJ~1WoPxJK?zZ&iN48c>mRp$;tUESHvwH(h56}bs4nEyMCF!sSL;S@ZM42?-POI3yEoc+WZiYtc({}0 zEXlqmeURsHx2^5}@%}!@-LLhl`3={dw~MN71R_NZ*9JCR`7u|O>Z-c26m@ynTczr% zyfGSe)fwOJII3$3K5$g$7T!7P$SwHNBdI`!B29uRVQj7!kj8_LZ~X2&&)+R{z-5@;8@K9PYz+MpGQFf>=%k z{u!CwxMUs_>xjBqjKup?*S?kKZn@l$Cb%j!7KOoL!3d-|Pm}-N>+l{#hF~spAkHZl z=D@TerbmL~gb)ZjkcNz;s~<}nF<231bZGaVHg)V}$9WKsIUr6L*Z;7Ot)gJ-6B&SY z2%DTX#eiqc2ACjIC|RtL>sZR(yvns~i%>e?6jZ_*4VgFS2lYvY+&g@-+MH_W-OcEk?$sR#Rh zTzeSf>e?CTpX)P8DA-^O~Z;NeEC(*TvoPKhPoalHiOWrVU@gm=~# z=|YCDwJ!{|oB@>+EIv_fEesV4yUk#eg6U59B6C>3W&P3eBa+Sv#*a8{y(J_4(Pxy6 z_7|txD@bK92$QC26r@VA+Qmsynp=>L?tasEN|cu_93|Fzq>I>jyl=oHO@U2&F`LY= zjfkEw`ft3I!^HEzS7W3ir#AlG{1w`pC+P@t=9nMvyVFcA}-W&IJ41aVn{+M_JXZGH5{PHrb_b%TwHcCF@630vK;0*N{v@dnu0eFZH!CX>!NbH&5b^uQS@)M;L!DHOc4S0;zHz_=XASSpSz*B_$L@7n^7ki;4L4Kl?B6xQ3`~Z)!%Or(oF@MerE6>;j$xytJh7 zKrbxO@%pemSUkVVke?`}36E%RJIG5y>PPvr`72sw#~Cdv`Rzefy# zWqlCAZwSDUn&`6xCxrhg9?68t2EQO5GiFlw%f%3E1HU1PL+YnFT;{J3{!jDoCR8@~ z1^JjUlfqvihQNd$-v#^s$Rvlx{2d9H&-@9gXZW-EYlCan4ChZt%ADih&0oHenK{l@ zx~sy479l2aFviX>qa&mDl}o-UWodrCpYEoBoKBZSE#e;6K8w`OE8FFD)>KF3OPbJ6 zHlM5#zr?in>ovKhO)Qug>HSL7Niz$4o1R{R7k25iH@m*$eyJ}o4_>Ey#wVW$_!qdk zkk?&tKTrRpb4l(?XME>RKn&KxpV~V4Q>PKu&BNM^cx^_!HX~lU@wMFu>u6~SYh`qF z@~2KCtkVeV($YQ#ShTeCq?L;&y0O z--`!ePggJ)xB`V+98~9By*n{PkD2>eU{W^oEw;UCal)vl5`;dP ze_kF51To@a8v;V;$Nb6_$Wg6fIV?F;GY`n#VRxrOEIP|Q!bBd&iy#ZoCI&K_3o|hS zi8Ywe-gns#-8Uxc$Q)i`LGem_JiVmMg(30x&ujA%m)%@kg6QEzcI8UE$O!G9zv_Q6 zcwuHn6RFTdaKq+)4)d13K9#5p*s#D;)&(eWQp zI^35MAiK|+q@i$@o%5eCuSrpe^Afj(Pc1EA0U^2|UZmeNbLz~?DL7brvSN(mrJzw7w=g*%U9eE<23t6{zbuw|o6xt)> z`JtsN`dPP`xxoB%e9sjnKspu^OIJd33tE>tnqM#F-9QI zU&YdVUr3=!FsAnG3`H=O#u@`bJ@cdw4XSeHFt@k5{JG^0EGri)7l{ zjFrbqpu1|c_Q~flPoB{3@fkwRnUE^J+ca*#dQCs?r<_5IH#04Asf-}o0^(UDcfyzt zDDeX0k&Bs(l*%@78g*gezro-PlF}tibPME1&(RepYmmaU zZAkicnKzyzv(tr@uU1{l*pTvK60^|VfGbVeTcs*hzW(Ufp8o37QRxs1AM@%g>D zxyAq%m3=oa|61Bc?cNwHPt~@q9gWnUU>CXyuXZJ=Sad(K=({cLxzP|SZ&Axz?BKCN zI%4&`YJKm`_P0}h*!CyKz7K2bJ_}%paah0PTzl>n=~~*mWKW<~m2p|?BN6!=!BUlQ zTdih-oErPCBXe)|2ae3FoDT{dS>*)LA+;QLur>)w>rv}_Zr0y??uT`M()|5qb`h-6 zs2rkBwZ3yL$nK}vkaUQIM$Q{lsUf9OOZ>haM?-k6?vC{lB3 zXNga|Js2B&TpfHolJ^7>*R`#;pN`a>p~TvK#k}OBY9mQrZ6)`=-Ju=Bj^vH&iO)sq z&hISo$+ttXQ%|d>o{r>AAaMn*nLZk+I7W#{F*zpjlDpMLcHfb;!C0Lys>d%z@-DGD zujNN-j_<6>eROo;l6vG)ByR+1MHceSyS?>JTtNtssz>!1u8?kt!;+>^vg520vHg_ zKAbqFy@KH>TqT|;bmeJNSfy_vuOTE}wP_2nVj?v_weAs96!NNq-Pc2}x~@4lq}-T9 zw+F0OvdaYWORl*#w*=v6tR>#U107xxDc^2=Ov&;!1DPZ^lVfkO1C=5&2VzoZwwR=M zlHkmkvzkH=cJ5<{qma}@IZj?bd4yXjedL`WZxNpD)X=}8mOZekWs@RA4b;rUF@M=% zwC?MV#420W%GS5~V(o)!`(RW$NrQTCto(plegL<~>2C+s@`JaeLs}0|t8+(f#(Ll) zpkeZU{3be6`UQzqnn?OHZsaMF_glV7iFt#=W=d7WHdPJDmYMePe#jB>_`ArSo=uLB z&10IsA~op8TOp^^XC32b6`Aq*ljyF6#|Ju$V-QG};!7pw4`yfDuy#!Vk;-r8C^VOZ zL*=XVfzb=Q5L5XYy*^9c7`!)i^LIBXfXz}5MPgGG$eY80@4bO2@#;-O%7%!$X&8ML z=`f7Qwp^`RK)OQ(jWlV2b1@`tJXY1FR<*tLNUYJ3P=@07E|DpJOH5 z@d;6LoU-0C#w2C0InmfnNF`WBpr-=rh3Tnuh{wNabMBy(W1KE7k29jJanHBwnk_g` zKO8Ov2gMhHCc9smmPf!0f@%wKRG^u%Lc?GX)lt4qPc%%pfkYY{@f@Px^rUQ%C^TFW z4I}=KG*TKjjTA9`(u$T029OJe%TY#+_q80>>zYYvx><3x0y`=T(=f!nsdQrjiVs zr?fmPG@%q~E9KLa%ugP%6|Lt7Ng;)4rI1oe@DRQ-of%oznVC&-o=v$sl9qaKX?!F>TxqDrV6~wwF2Ri$LOJupxFIGok@#_*WfGMYGbYNR zJtDzDfgno^0Y@bvuouB!$umOg9{z0pir~kEs5#9RRMQ5#AdeluPRb9Wl={jC*olN; z&ZhiO%|nr%*vkn{s2J0q5DBlm@!2fcX+DCUMa$$_TP6uq1Dhd85|e221Sb*?bNFp7 zeJUX#Ud5%@Qi;b_ev*`C|A@v`1BU>qfkY=d(byV4%VU{P*~XV3A2TM(5yMZ)BchaB z!@rNig46=l6A}E0b0bO03p{7@*JmiLKy{sWDD{}2$Q)Kf$$^%}F0E@%;v3dxAi zgpxlYcf#`%oC(*g!(FVAoukSU&?)5#dC!p-Mxyucz|&;6Wd>FS^Eg3>fM9kE)>@{$ z3Td+_m*9nK%s3b4>8c$=UZ)G_3e5;U-7x)49{((oLWBboJ+!7H8)|%Qy}R z*q9KK`it^^sViRZ|UCxxN z7x=_U-^B2Rcmext&3%B<50OU;QoI<%otXt$E5_NWe~ro|ksDPQU1veMb z;A!>X>Da+h_26japnf^8DbeUv8`+sNvy#0xJ7Rrw?}Apzq@sXNMWyylq5$4fOrr9n zdg@6{93#8GkwYz2#emefA0$&*%~=~;*ThJ~470FBc7g4s=iaQAgI&fBuE9dgb>DjV zk(;5|u~GHdXjB?wQU>k9U22b*)TK&YbY$~_+I``cba6BLoYpP+|1k&>lz&UB-1o@) zUua37Y{6L6#K=qr0DhEy@ZYi=Sj7N8hc!X@L&||L+bO&m&fqeWx%E%D%r-8w!7Wz) z7>)C0)`M}0|Aaa#feRc%SX{Vz>zmLQ(Kq)?I{M6}Z+ znlMSFJHJM==LeJ|9#_;IbNcFqsidFU48Fx8zF9z3~7norHsb3yu*2`La@Vg`-{ zrNbcj&oJs`LC)f7PXw6V>)QnL03cHcS+z;H{+tG4qo^4KsK{@i0fOboLO~1gxeYh#QAq`dDq+tp2ZtoH6_V!7{U+cHS8)cf&_>JO1Cv+zU#Z=aFm`JgRGTaU z7-u1W3sq25%7D!ZJQ$7y*wlRYLU{Uwh0EK;oaZ_mi`ZV8$41RIm|AYR?=9 zYgvD^{D>rY0ug{XZM`KU{qdjbSw|rY)EJ*=`+p)VT$}{Vww(mDGMq$=Sbfc7Y$Gv? z4X5L`Vd08cgvNPgr1owery>>`V0T$zPm+=h93(iCb)3Lr6O?RO!ICi*>4cmprM{hl zX-x__%<<;MOqcZ_V(lI0&*tyQfceaytS0M{q&mstn!lFwf+@ZNuorc>%$nKQjK|HC z{K<@oQpLSaY(`N^y(~6fL~}~SdK90A6EQK3(YPI(@kgkN#%3&FW}I;zK;P!Q?p;a< z`P|Pr;G&nW#0{!o~pT}NX@k;+& z!rFT~p6P$f{i3!eOCj?9bLZSVOfSn8rWa`o(*czC`vhq%VMbaC%f#uH0a-u3GGH8Y z!Bbl&JC=ec93#mh@U)28`XlNUHl7i=fLqnmPg8i^MXdFd|AHn|?vVF4{C@l3U-=N5} znOWGarJ!*rOB_?tj%aXY|1DUq)k)A{o$LB9iST$Iq~RxASF7&--#^ziiCVJ3ay8YG zIUBKUZSXr6z73`1!7a3-?>Zz>sF$EnUzDIwZ*nz}Q>yNT5sliF0NZLK5LenD;j8Q^)=Cz+2{w&(v&B+ktYe zo$aao6>ULDnVHGfMV+7de^M8`M>%Zboe?%f|L=Vo-dA9CMXY1au|(2!U>c`Z{u{!` ziB#IG>Q1x7TkA;Rt?8SJ9MntznyfT^YmjN{if8K4aQOu62rgX>C`tw(U~DzqM?sQ# zruiGsx$MVTJZ+=0^XRh>Kki8~UXyG^AJBe6qIR&{cGs+QeAD}N?>7#syPI|8PRo+< z>@7(%_XS;31{&kRISI&2^3uqA5?R5#;!ZUz8Rw1Q-h$~yj4Z~@B4$>w5d|=&Tcgf& zJDoyYw^4)Z_F-O{v;B?(p2l#a%gv0$oW_x^Ma7@T# zbem&`Kly$N2FHG8m~b+$rTHr~s3v57;I0WQM&uHVTZ!0bSi8tEc8JU#ZC;X61&otq z#;wE*8lTmin89ijw*PjTl&v;^*~=z#VjO?jiJqanIlm;WAYifm)LQ)TW~h?2xXIk} zxr~uuXYUONauc)5^pV8Ihk%s_7;?8}v{{*r=3OITH`a3)kX{TA>1HK3UY{-CA&pQ` zN*%Sa!DHN80(csDQAyzuum*PkPa*OXr4+$a#Pb6@hNgN_cnpnH4*8DEaTOy!QA!a! zB^G!H5oRtaJXe_Q>9-g^rGQ72(uC&{&yO!F z=q+Q1LTW-(6WqvHLI7TUWj1I;sUF*$YWxWv1~Jr7b3v}QQU%O&Uj>hWdKsb- z)Jx_io4-j`Mxt~Q+)r&~6x1+bWfTj~o=N}^uOkzi!%*)}Zw}=x#Kw_-^LL~Y?2#i= zn`81ZWabFbLFHB&%Ks6WB!~_;^IHjGy>=IC9Pnj4E6ne2Qe@yRtlfJ7Dl2E7tH+~l1zlSLNpcs^JX zn@r$rrHx#jghe|c?q2M&44|$>6%YW&QN3i z1Dc$9NI^waZLG?pR(V!GAE{!tb2LqMi}eExS8lB6d&(bv{=2YkaDmA}#Bq|#IZmnV zr=rqnmX<6p+!+1Z#8)R`RXu7|Po&CwOX}N#r~T3G$84|mU7uv#wTeKV!s2TYfnWo;UVm{WB$Fms^0HL8e%KBud5L!~rX1FuM z2Yi>8t~Otuo5syWnq)9&iUy~R8#4(*4)5~e1~SRC>!fLM>k6rG+A&WErRdTqB z+1**ys;fHYa;q-)>R#>Ite7i#7+ukI%eC)aT)$z>*|cFIRz_|yIqSn%vY}2oWvOfWQz5@DaHvjY(VIdWG+z8J%e< zH)X_N$(!YlNoEYNj|+$2$NJ_77n-Y}zPg87t=3RG$QtVA3yEdp8g9C8+W*(+!fYLR zVLz8}W=`8wyk5FNUT}3Xzm>sqeerZF{cXPO*t}@nE7#93XOCpRW0(quiFvOVTa>6^ z2>3p`eb!A8dkgCyiIhS_kv%U#YN-# zo!Znzw#Fy;HTX7P4^Fvm8QXq!_U7oxo7x*>S62Whcxf{@);8}AOii{6F16-uF6v>k zPPQB6y9557wuG#i>%E&!}%u^x`bFh6f@^6FEn4gg!V^_?}`x$}- zqzZ=i-P+e>avvuyW27Vs-{fy2O(Kcq!s;WG+us2K6j&Ep$OYK9Id{@`U9 zGeemRDD0bM^7}TIa8ZGIm}MpK|JQbHJ#8FOc-I$VjGYDo7z%MV2_ZHiCdQOpTZzy> zp_Ec?xitl2FT|C>XxGlgYV**iRBF{qw5rpKl1P!rQ&p)C{R@4nj7S7>D>V<5`syaG zc&N;L5eH}x9&+iyXK-Kd*odffWad{q02Vv46X*a{7 zl9;2Ufj}U=YbbVAE~)r0&#Sl&(m62ND^1E^;)u(t%Jk*GY2Dn*}L8`lsq zX9Hb_8ZrHq{wq%X%F19`w1i$wf!tQXwX1SGu}G~Ga5`Ccv#yRUMB*t}s@K4Qi_PNU zsM-#zHKxwTBKl7tl}ZV)*eFf{38H&K_l}??Fbb%f(bU9oGs6cTcsx>(B>EvFVXgowG(Bs3UDV{=!wO#mcSHu#3*g*h}d zF{(J7!-=GPQW}_*Ky|@l<2)A4bF`gFFQbxLq*K+wF!hyQQ*9|HXnshk|(vLXvSDF|r z?8#UXr0nh}ULAlLLO}O($MHZJKX+-P!?;qV@_uBgE7@(Qh%?kz?O7fD~(;*Zo~1A++xD<}DTO z_NVsBM&~Vez82d&xJaz{Jy}K-u{nc{GVEX(K-y3~L$kEj)VAQ%IDB>H0%Ifr zX$kAO4<9-F+*MmN%`tq6zV?rB)okGwwOMO;OvcgJQ4hPID3`$r53bv? z&f}8Z@D(pXE)(>rA(sJ&$}FaG?Paz|cDw|+OywJL8J-PhNtKh!WCy{PdXau~jP5r2 zPd_t0t%Y6cD7U&|eAtQBICk!Q6iPJrOvX>ECNnrp1-XN@V)BL2V zoaD2k8Ac*i18{{AB?iW*#DelSWLLgHkb7gD}KBl0Kf}0eapnwKC!)nY`5tkZl(sN39QRzYfOdruz5%Q|! z!2e|o5Bf??p%24z&KPVKPHCq6Ocz{$$xo(KjpfWqx%hcGjiZxFKJpBWqr6rHG_W&Z3QV&4IiR%UXwxdF+!?+-4B`&P#lU8}{dZ>`>}Ozt zm?i6*Gkr@Rl#pA69y+?#9hmw^6S;3$-g$2)OdsOp1$zrh zu%iTM26epH`}>yz_X3QTN-v=q^EH?K?bSM;N%*{A+m8ZlD8MOHLat3!K&8AZ4sRxj zDzGZD&wo&1&No)9-eV%JAe8~G?Z>aw-EwbTnWaHS|DX0D_*#I^9!LHMR~R~X^IzHR zeno;Fc<)8OC4F#WF-oYYv%1lCH5^aj9mSUFq%iDcQr@wxkE`xSX}ngDnr%l=f|W&A zbM4O@GfNFeO>7X@a7{&A8QD#Ft9tbb%i^d)uV=$%14foBBHG)Y3|hPF z^&4@~YVQ|mt6smzmyRf#zfGY}RC8AKhN|qNv+9u>V^}8b4!ox^H>&)7!Cn>29w9}J zzaU7#V)(2Ix!@IA8&H^KL;94mlLFFBI8hI2F$y{IIYKPC-*Y>SoAo9XnZJeVDaLKY z^rvEcuYUtYQ~=3!9@CJQN@}IRM?nY0HH@9@9S5o4)Ns$_)R~F#ah#5gUl=;ezpji@ zLNeDF{R0CBV_O~B5*%wqrHPVIA2YKj=Qur|h^FH58_EzG3poY#Hw1aBD2o4BtfK9y zLlCQe7u*{byJ*=E$^@t08hq@qTwc+z%QbUb-qtVh^cA%^lbyPIuUp`p<3 z6Wex0y|;#{SQ|wXk!2X literal 0 HcmV?d00001 diff --git a/src/osbridge/__pycache__/backend.cpython-313.pyc b/src/osbridge/__pycache__/backend.cpython-313.pyc index 75cab98e32b5606bfe9a8b922145790d091209e4..a1bad9e5ed9b8c4ef9e95a22700c6bfc6f63a650 100644 GIT binary patch literal 4470 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 diff --git a/src/osbridge/__pycache__/common.cpython-313.pyc b/src/osbridge/__pycache__/common.cpython-313.pyc index ba16673cb8db41e98dab8ebe5a17c8da6c4e38b5..6800d9fc84617599bb33d88d267b4ef88d468c41 100644 GIT binary patch literal 8602 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 xX@aaf^#AGV>KNvg{Qp6D6qo=2 literal 2137 zcmY*a-EJC36kcre&$3`_j2(xp9Va$wlK?jUiK3+ZIw)YA3~rnj$y&qK-WpgmixaX} z`v|^?)JJGvqfda8o7Jcnk@^6Q^f%`|w_~@dNw)5IsZ>mbhPCc>QcGwN5-e zwR72(iF>FF;pJ z@?IH!Bfxf=z3lZxL>+2h4;5so=OE&%OG6neQdbY6b6LV-Ma6Ad59UX}*?=#o z-Jw`gq@E11x}U-3$APQ|n7NOoj;srZT_`0s&z1H?U)D5zuA=lsi*$6s&(Gi3;vrTC zG9D_jriW^()DcVaP*S>@9=WAO+_*SM>5*H4EGA^BvNL!`p19HLrN|7GN8FY&-V)&y z4^JerSUqkJ=$Ee4Ka(G^@qCa^^_b0rJt0#bT_|UBX>p?>i(8rW+f-iX%bRJN*h;4; zbo_e^LKq9rS>7$%o7-XDoDH{g=@gmHce^A|oSZZ}EUfg;MB1!ATfquDL~$Q01I8V+ zosWTj6V#rDbzOn&6L$ok#nWSslV&&$F6Pv0*Ba%fZ*em==VqLT<9>!r>sf=GH4Zv{ zTvyu{Vn-TEr;;X%ZTpg`eescq<>4jnYoaFg23?s25wA5kEj6QukBZ`EIya&#yOsV1 zXZCi##W@RSb6X?2GTS@EO;5urR1o(vTcfSa-e_}|23nXE-rZw(cXM}?-OG-$*=>e* zw!z-s8D;i%a1eS*xrp#%zIIebC-r=@{HB_(rKU&#+Um+DeYoAkJx~Xi1C5SR6v>PB z)7FtDW3AOY*DlXnjlNvM%6qw0lC_JWI%qYt0#-U_a!bSQma3gv0KV9s+J8LwNc{Gw zI;VI6N@iebbThaw-ssi4&)y^v-j025O@Hj_!)O03q<{9W{3RsEzN8uCjp{Wdf&7Ei z1_?Ib9hOh(jnYxAOacrvt4*+ZW)~avLZi?)CW&w0N%06Z8};f>5N#)}vjk8Kr z>-l{s_p1EPDea?nv2k!f{M51ncxs}f!{)(0Di=w}fu#v@25V)Zm*xB$%gi6cxQUx) zl!%wUK!#=PzX4H{Ijq64G+(|VQ5IH3hbNWl0b>bYqL8(EQmhlEgi%bRFi9vjYE(>Q zLKMpRI%RHZPBeyZ)vTZz2~UzN;WyY)FWibRG-^$2V2rdoumYel)(=CpyU49XtDPG) zlct0A-J}iS7#uk9*-VK9Y>Up!v1wuFK08I&J^O>h2}s9rR^yrV*YX_y0MG?X`xmya z(=M0mOE_)>O@7V@n2{xeH}9m!T*4F*Mu?GLJsboC%;2KoGea@MZ$?Cj67GUuScEOK z2prpd3x?Ya%o|=a5Qj|=jSytde>di$W@H_bB1zb0R}I08t&F*dIhUla=shEBM(;b@ ztO$O78Gcfjr@s-0Ew;+S*B~4!MmEN&=i~4TBS>a-2o6QBo{fVd=nF58li4x93CZEu aRcp+zf)z{r_WgKv-4Oos_*`K&mHz>wXn(o@ diff --git a/src/osbridge/__pycache__/input_dock.cpython-311.pyc b/src/osbridge/__pycache__/input_dock.cpython-311.pyc index 7b79eb295747b27d81652df2e0f59a5dfde9cb09..82c1220b08fde5a85b5d93fb9fb596cc06a99802 100644 GIT binary patch delta 5402 zcmb7IeNY?M5!Zph4}tj5Cm?}@J^%p{!hmgXsEtWu#~-m1uoHh`8Ob0F2#zB*#zGg|iE+ugUn-F^Gs5&b;vkKd-{{z9!*De&{N{_Nnk@H@G~dg^PK(*Kp(6zMel z8By!NV`-w=pvN*)x&g~AsC<5ZJ3|MUt{zt=ZN)?MV}v{kUs={uJ0feWh74*TvfX)3 zN$rA9UB%Q+$f)vALy?B67fsYyUE#z=vZ%?X#lYi~Q! z*lbulYJ~i?8n`-|4LzZJ<#;Zei*38$PN)!`8!3RxQ>OGhZ0$g(SUH~0=97itqmYKm zhufnDSRB+S+5E5t?rQb$Ky3jG4CKH^#@u37(OHI}h!3miP4HnG{H{(WP!ojf;2WygfQ6}XDu=nzwT0+Lh%;b_@nhRidqzK*`)WFU9BIS4iTacQfbPcP4A%_uG4;4}x@CGZz&}wmL|67kkt7COy zXe%4VY-vTP`!?WgX{BtzbWjcdS)HdG*Ry(2)UYNm6T{T^=7}D;iAR*AxZ)U&kw(n! zxeX~vA=M~87=MC~CFKB)cXo16O2WaEK?yy)JY0-pmK0q{>Pg8Kh~cP!3nPWVu8)Pn zz#5i>!pIsEp)kfnQC7$_#eGvK{N_s(PAC3J&v+4AB!Z4rbD3U0c<|O$Ko+2kH-VCcqE0yc$nnzVB^Mc?(DHBB;}jQp6YTiGAR*v`h9Ub<&OEw z@1dQF(}ZDqCtMI3$d31*b#b?_sRSRxI4Q@tE`~8FB$fXZDgWf)1`MR4NuHbvxn+Ol zlF*ywv$59ECT>(IBY{Z`B^D_BX|#wEdn0@`Y9^f#O2=H(^1^#QjtCdw$t3UBk-~$P zF%`8*Y@PtqPdVUfLq0T*sVOl56E5bZqy|nv)rI>fpc;GsU|hpr!^qag+HU#!qP3-k zSHc>@nz+6~0pA?WhF?FeNjJpWX46wr+q^Iu>lLMJ>5^VyVJ(SXVM+7~Yf7*1q*l>* z8CxdyidZkTl3t-NOBKS6)0%9o+S=6FaT{wBO~iVvP3o~waio|M&1r3^3fP=Q++rU* zqe(A~!Taol1n=L^#NgRk`x1B#){%hch{H>F;#0wT!jxXlmWy7*nx0bxQod|u=Y4=w zuoZD2B&{ixn;ULaaPB<`Nj&GeU@xSBzVaOID$_E>trsuKs*xjASv>F9;_tNaidE-aK$`u#XW$I zCNEqDpc=U$xW* zYnLjDa=`N=F1>x(e3n!JN_IuKL%fi5P8smU^vS7h>D6qtSYfxO3h|}I8#IFYM5%UF zs)_L$wg#7p0p1T*Q#J6h$#AB(1&ia2gJ0LJH0%7UmuR?IcS6IRs^JD+r-l9_25jM( zBh?um))Ur=x2g}0XebY~78}6ZSAn_H%|^&$a^O3>uRM|wJE(iJ;9Oq;9>8}^{hHKu zaSeMgMt+w84m?|rWo|2VX|ATopsg!tu5@Uav;`ocQ+&88yQzHtS3^RzIy zSqV2wTExGLTODvmlSMT^$;l!-ngMDomHq<0hMrQLc{l?+r?O%H_G+-?=fjSNb0eot z@1fxHGdFBHyhM;VOv_$ecP)T}43t%R5f>3}=0Y^ow3S@ZxQ%!5*s1ZzuV&1YR#bjG=u6-Pz|m6zE$(B2R~oF64{c;b5T8 zFU`T#b2f*nFVq#cl0|sQL=OH+#KUx;^AIxth36dxRd*ni5G1@P2y4$bY*Q0qGSp3n zMg|vhXRBpeR2+;spY~Z}z+3#Z#YY{$Gl8g|^Q|Eh>wIp8DtQqoW z)nIMUTtVCL2)!R1vyL=#L1gpnOq$DawtBXDI&U5s=a4gsoQn!nk#kppveaJ?f?u=L zUuqQQ3b_08#)&U86-AD->RI))`j)YTEA{ZkTESR5oimSgbI2A&HqO42N4o^Hi$lBS zk$w(YqR0}?;gMTFZVtJ{F>4fAd1Mo?II_*7LP<5;!lOC?)p4i}uVXNC=2~9w74+U| z)jZP7A!~F3S-G;qJn9xuH;1~Dn$0}&2*|@B57yLKPPe?&GL3GT$~apiZ(1doR&l6M zlrE2=a;{=8kM;>@ABXmdgOyQKd08uS5j(VG>=Q==?^p#j7yD~(qx?Bk97V;PshUSM z0;=Iq&3t@tra>?@T=Vg0jeyp0Xw5vl_J$U2Ui_ALW+*$o|E2xYs#`j&?BR8_g06Nt zYaSIyVdAVEJlZLsogCT;bq914BJ7eVD&dhuKo$;JJ~7(D&Aibg7(KF}FDldyoHR8_ z8YcJ^6SE>qFCggH;i`Bf zC^x(x9t7*<{~vWu}JLxdoj&r4Umy^Vsst{4jvpIV6_bfy9P^?=Bf5uU&d$h5K>R z=Civ-XgV;!bOequG&xWfvJM9a{B1qKKEL$Ou~Kp{48hP9BlQIQ^h!Ck2d-VQYsml6 z7L>z1xQl1fzTj}66aIE3K&fEURVVdLWZSPmuQ4L+p^RSGpJWT(@Zk z@cS_N*OApK^+y|=ylzV?Qo{A?Guz1(k3K>OIjHDKLc}9#KjBowmJO2yb!TT+pKo}$ zE6~^1+3BL_9y~vcXB6uttDA}ZW;h#pQoA=Z!@a1G(Xkw`z1u=m__HMhgq9$?;;0b_(w2rJ11Yeo!h})gXp2k8Xr_ zC1o)6^AcG8v=%;Y)XK)RQLSL$XAN3d(r3D5cKSf1#55{UtAJq%Cf3@F8>qXlc zS_^miD)1aIV_9t607q9d^g}SV%v?0aM44C<$sFAIv6^lsCee(bQOhwRyvM^x^%&*x zRIKB3)>72KlB^{UNS9`%hl?rdq6Fs>znO=ou)3D4mqo&GSx)3b~NkhQc}XwvS=Cc*Cb=5tc;ailwoc7&1oycF_{^6vami_^0wbYlJONI92(T?PR610DXpHO?<%$JT{+` z!PKyI18XU?jI|aj!LZ8jER$c9VxRGwdB)14Wny0~9mQ3f^PC(`!h`qE@uKI?7JBXx zJa4?P?SK&Ewli&zIWy0+isPlz7;}Itnddo!^Dc@u(u_(T#B?b5y~^ zn28onCAc^9sT2e7-KwOL&_wyt~qM{-qtl|T30xB;p2&HzQ36e@(s5q)KLFi zWhT|&d(Hx}VRaF^0QM8+qw=(pdB&(ni&6JyxHaVjJcZ za%Ik>zHvrs5>w+ooH zVop$61%G~C0jGm{EWnq2dDDYB@h#+xWxPN2!V-+RD=SmFn>Tah+B$56^Gah}_TuX@ zM&JF!u-_5h=fK~0qka4+=Z9tBD?OZ(M{@FWFi3w6myTV8HiiM+rV8MX+Y$FuyrZu} z0_~e8AR#Y*>LH|A;^rtIedfKlt!v13Z4B2(CVi18CF3=9lnQyV(HqinA6XICjc_0-;3* z11#SUuXP(0L%u`#5!_K~;6it~l1Tggf&KkF0fGH^G2+OGICk}vUH-6duRjPcomdEW zi*;ZNC zY&b8O`Wq1{JtY2*66?7=L{P-}0ujG=R*g6E@H`jTJIecKt-T&^H|GmQhJBnr#BT_5 zo5G_J|M~-X5CP-a9*CS>OZUL_vsH97K6}w6kasU1nyn}tdHcd4-WLe@x!KYO z;o0eCkjGrGIkr#c!tTH?W3FP3aNKVAB33QAuZfqvc2c5UkXW!fXW@O%{#(9bPD{pqE+j^45-n6lIQga&_XOJs}T(Pz!TAW6U6KFBqc=KC0cxib2 zQ-do}wKQpHNgG-w)wfYu26~SwtyX_Yl`1s%3Bf~!=BtI2%l(?^f+>M)cWmxNU3=2z zN!vULWVwwjGpIg=>MxI6bzXhsCY?mBY1EoPt-?%g3f0C&lE|G#?gVn*MP+GZok7(p zG+rI+PSxkW$;2|cplmcrLb{$NjrZj3wpe8}n{Ou_;|FY%U z-k%M9wD(uxWXrZB>Pe%X1nRk0L@97E%qe6}A}h8xfvg3})6VuJ@}v>=-g6fjW)RUH zTbM)*Y1EKF4e*ak3;#!wmV^E6iuk`TZ<6ZFb&0x_NzV zeGjQ)Ro^H_O0^pwG92lqS;f9Ui2Xr-AjoEZLpn((43io8zF^qL&nkO-!BPKuTv8m@ z3r}9JrFX#V*Q>N^2}Ty_4u;*NcJN<7C?q z;r2k+doHFAk0a!ebvQDGSOY(L&n3Afg-h?9?CK)G0YXUoa->l+ExMa<3bI!XCbJr^ zw=d|6MEd-}px5iBx&3%Pf@i4q%&z2ni0g!-zc^63RxV$3k1C)Wm0) pI6pdJ%?FkE*Z5-}Ow$kTQPbV@J?j7en=1Y?GkgDgKwLGp;JcgU;*rLv5T8s z9Lbeso%3+0hoe(n+Y|={`%|3_w~nRWhD}RUs@>*Ub-(ye@-9# zS710kPQ&qa$s>6j7bKP5&I^u7r|O){Q}ZVC)%?isqV_O>+0C*YA87Gj|C&)bkWu8!G)zj zEM0o_+<4^nMgN`18hu=|e_UCME|0IpVu)XSbt1Ad7eOc@dFItfC^V)9{OQ807yWaA z5IoOB)D;6v@zsmLaNzVpFotwz)yUeakuDeyT#29zcM$(lAR6^A1t`RI^?Z1BEp{sC z4@H(xG{b>Bmjm-RDMsPdGpc_j(Cth+ug20lu1*J*)i|K;ETY+#aO3)M~eQaX%hBq2eqc_g_ z)whDN8>b?-!l8(NA$sF-bX=u2zY$gEZ$zSV`bUs3?^}q>-|Sz#lPeO$FbgC8@#1Vh;AM$2jUKiN+*Ek$+Jqg z1VZc1pL$9-^tYg@C5rJgIe_vx(~p8y{pdL1rBP*g^|o?o_4W}_XrCHfT8@n>`vs(&H476qIfF#h?QOJFtOg}(VnC<1`DmV>cC zCJ-ZJE`-RLAuGXf-*P}N;}9xhg?`?wj){QL(FHZK+P8qNm4%Dw+T46390Pmoi=f8A z@F+)*1(0xNi?m!z2Wx3(f>@2Nu$9#JUs=2Upfs&aqK7T@s82!w~Jl^8r<+L<~^uPAK4k$Qe^f z5Q0ow2&PQal7c7&8B#41`qGtt%wO(6XbglFAS5DKJ%~h*YNY5}ct8}*FG=OsYWcO_ zY)IAhYjyoUC`}EHX@g_Q{Bai6pyfB*8&BppKPjqwXU{jgQ$_7sQTsFJNTus;!LyJ< zDy~S$jhfte&-OmK2^L&E8epqm+L{#q@7K!pG!N7%bw*)C7n#-%$X!G zVUwiDuS=MOIj8);lhH@4iPiSaba-&=%)b3rYuK7ez(*D#ee~Tj1ev)=XdyG9pr0M| zA3m^epD5bq!j{>Y4`5u|heu}e43?E4vsk2b)xv?eC*=qpZ78XK-;kA+7#N!oJ`epL z@E`K;%NY*!sYd2}BfW!>n{UM*4Er!8!Gt)f9!Bd1i1uju@wW8L>2}FFF&L6Qy!BZJ zBZlMn__{P}hIu6}b3?{=OjDe!pMB2Yb+n^*#Oo$7=Z*ywzrqT%ppeA7s?0@h`~NtD z!1$y)i@dg>4i+3m=Z2^WaoobRjpo|+@6RGe0ZlCo(qCr!EZ&_v&iPfy%Y97#*_Sg* zFeq-bU=e%sFXGQCgN<4^c1A~I!B{Aeg(RD7&Sm%&RyGS3;lmcq65&yLg+#>6sAhK= zRC21FTk@=4t(UvosnT3m9mWG}vVavlUCi3whxX6ll2)P0!;Wdf zY1Yqc&ofUnUH0bNN+pir@Z|I9H%gv|&RY;aW_I3X&{Z6a+8S7 z11Uaf5LBYUg@7`5hrOTDTIMf@>|K;P375{=Mn*=CC>i}5t3)gQ(2=KKLBglRce-Vj zaGx%rA$-+8kMUY1sEnx671oe_ou-?*hyON>;kpNO}&Wlui<|*jt6M8r25N! zZ%=IG7yYBD-#4Y&ZOM>7!WW0#-(>UlKHg+o!oowwIuT!AD5QDJ()Y8^L3$*r%|xi zK=}4`Kd&_GptZj@WLo{h8eoAZAJ0Nh0iH#kLOf+!%T+7FbgnRlv@|d@GI$CXFo$k-j{f;}(?ckyRW(vI0t3WI7ev0&x65^$mUUG*_ zI39Gi5OrVnV_#-BWMAf*7W*>0BKsn7%}QskZecB{nul0#`=~X$i~Cz$9py0Bya1t= z$FSbh4VIg`JWl;@#Ha&0l{oEgp2B+DnaFH2_vbi6dCV0vz%=o|<4MwVo8{&%o*E2K zn(pAf*4t}mOX89eC(il-mXhU#Pm9?IOOJIz{fE??yKyhPg>95sI#H?~8wkS*mS#vV zSi|h&;o@9*%rG(b=WEaW>22B$kOp|J)_Zo4yQhb^%X-^FY2?iM^?}ZLNl_=7O|YM* zfct_vXd#sSP}4))ZM~|dOYhUBcDr-R}@p>zmul2SWv!tigf76bm zPmfZ>l5;eGVrm))`6&-`k%C4c+XN&_0Hjbs7TMDeiblW9mzycnK41@J%hYCs&1Y;B zg-h7EOPH2WOYv+~63(d@tlW83_C$!HS%AA0Ovcw%11jz6U`ijuPEMh|{+li$GzG#7 zX_vl@q{}VoDy;jh&BxZ%fG>7uHQ<}4t;2N9sv3C%OL4wXMBjuo{Hw9z7>le}-V9c{ zd@&MljMzmVmX3+QNPz-EZMSZt|VXVP3@kN(xO4$rem#Hvm3&bWMWzp`~B__Z} z)d`S=N@Ga6oWyj1h7^W{LzUOyEq`b&5KX)1)`D1NU~w4zsAnmWD;8N=!d@2Y2jPZR zFxYYj6OU9zdM@~lg|J1pf^1dd&1o0=zBa*D_tVAf>mo8&!F?c%86f9k;WQ;?0BM0m zJ;Q=gYfnrK#O9Z~3)L2Qsn_XojRM^|*7X&UP%s)xml@Cu6cqb>^C5o}J7`fV9W|`- zw!VetB>+3lvwc_E{Nru=JMfSGB|6LBye&PF%f48o$+bzjexs)GE2SH|n!e)NXjJaz zC*``0_AYw2Z0y-f@3xJOZhE(FG_}&ZVWX>;-W`u+S4ytedL#|HAU7FnWpi(b(QXXc0iY6=f zD+wVKgR*{q=sPFAec}hxzjx#NH?%g?Wc*2e$Nl;BdLZsA70B$X6<@@KUVUiNqNxp>CohkXHsdW>l3LIOHj2^aZO65JPGxbW3whV-_QR}#kVV1GWeLG zCbBs~hPAfg59JYE5O-XABW7ZNc%{dci|m`fn`sbf0LCyY(G?D`t0| zT8pBoG#;uXW@3U&y(3+W9<1;BXH1F9FjMiFLFru<;b#T_M06h>SWGOfx>vDY+WBZ_ ze{x_fG5+fMz-#L}eF>LuqpT)T-@9Jc`)Fs|`p%AotK;!bedjr2f1lV4!{P74VZ&j+ z9fw|Q@^VpukBokZ91u3_?Ewh%n>K}Z{hOYiJdSgm5hz$Hu-fNW0NW<=5$x{5c;`7x zP_brWvUr79JO)9qcv^^3=9m59C9u$f%dutf)p9eUFC2*lX3k8}QH^DPl=k=4HB^Aj zN3UUd7|YhY74vB>nbClCkub5{jA4c4w2*GI8gtV~e};^rFXQpH^tiO*or7OK@u;Ha zomgUbU#g;CtLWcwSEbx-n!7FK?$_M?4`Ls>hq1ov9ssj&@7f~b+K28D7BROI5r1a{ za1Kpr5x`l89}@8_0`;G$p<6)!nDOMz5)rYVXl4S5+#rZGT>h;l-NtXR7!u{u6Vc61r2#h z;qsKA6BbS|*(zS4zmS65612&bRY+EstQ2<1!dkJn#b8HlC+|uX(_%l zE2-iM#Nw5Pr}Le>ZmFmnn{-i!?akXAE0wy zzNx;#5<^0x9qx3|Ex#J3Juq5Hn^;rngv9yqLg03HzAm_-r%CCG`Algy?Tp-vQZFaj zOkWvm02j-Zl?`Usk-_XXi}sfQ82z7k=;*IdnaFaLG9?s!%%RaU3mJ({~G<=(Hk_dmRx8a}TLpI;xo@S*$S&nkB^&~1O@ZZkkvU43Mv zFD^iOmsC}o+S!kP2v1cFXjKELsuNn(i61#WtQrR+t{Qk&A?>Jo)*-nXb(RcS*8G_) zS%Kj2n)F&Vj($1@90OpsSLvC@p84!qz@CLy9iF28!j)=kT%9o&4L04nu}{ez!cn`} z>e^U`rgCNd?42Jm%w;z#ny1u)F>9`TTSvpfPOwD53b)6l|Jj(VQ?$9L5BH%$^>)=vy8Bb9)~ z^4ciUECyx$=W+8?Z23x5no1sHv(ni22xiTNEuI^2U}6sCZsIvx?`aZdvwof(R<4_* zYRgK3PtQ&()oce37pJ#kZi-_;S*=ty99D7PaDlf#*rMCx&|HQ?>xgh@QWt8ohu6${ zKL-v)sa^z!g7m!Jn^A_ithQ9n>29kI++|--o;4R|IX#67y@k`}k}OXH(Pl4qPh-}R z{j=p+I8BqDte-8lU}-fYwWyC&+hRCHJ=R-j#hA_IFGb#>ZMl+30iGHT3w?`DXR{x0 zEm-yyQlp)}6p2bJhQ)J5YZpyFFMrv^Q-x)(EG&(^ZwO1{wT&>=!ZoJ{-~fWAiGxGj zZM}tkqn4LTApVTc)z{ zusRMaTQf=Q(qEv!*BI$@@Aq)2c2v*EZaldsc zG4Hc&iwJmMiMQl4@xGGJ#QREwbg@0}vrX+R*^T$#aH+Rc$ouLo49{A;uYvob-6_5Rdl5y0ZP0-NHVVxO_*_LK^5#b@d%6`!f6R0uJ#eNVCRblXVL!Eu2WFy{tn zff)<)+$GF+4^Pi7Hb`zEKe5UEvsSITdAjfp?+#I|JbQTz)MCfB zwb(KJ{Iz)fQ`Ew?PO(XkGgnwpUddZM?DX`|5xVeBcAT!4xvM~zqSc|f+!6^}fDY#M z5hM#YfGC;|z6iwr9EbwRX!>~}nyVz6&bStyv$nQX9?;%WZYoaivu4orvgtG(Y{wQW zrb`1$1Kor1J)ULg(_PUIZdgn3rcRZzz;w2MS>a(?R>~!%mn&}J%R3HOHg9D zyItwROKU50{$M))%33&prGEd?>YAD^n8E5g`;0CJZ>CGe)}Z9LDU}ke}kn zV=F2)DPqC2d?6BAW`2d!{@@ZK6?(9Xp+XzpgJhu~o#-4V0MJ7P)Ju1w^e#9DrSxFB zXgm;F@~^F=i!Yf`~OZQK6EFNDwuHwtFC5bjiPf8U)i7GePW_E(cYp&iF&fyujlRVXLB){BbJk ztpNOF`dDRXIITt&ko;O8#7bT|vy3x1%WF5+77*rGQ&98rW6RlP5SIsv;Xos!6-4zf1Le_W&{(?o0=8aIo}2!7$&7y` z7*ddSEndo+emp=e5qD=(aga``2fyURL9sv1JQ=3jA9$`)VOfXuX%ojfIz?Zm4Fdr< zv1XkB#zl5A{Bs$lq-f6r;3@IYr?lK0uhcjFCL+*M3j$w(sQ(M7eg@dB%A0+bP02_} z1ri-V&xy2osR;yhi|J(gXs#;xCWwWGBM1{m%CO;=iy|CJ6RS>BUR>8VSACj7`LR13 z+uKSr-HN)o!W4^>bMo%_iN{vQ(Rnh8k)z-w&Viqc%xMEd8^>r0VYKbhz4;+SrUq#q z&J>-Tw4kriNrH=Dtg$tm*6jN6!|u_z+fHhviuCk0BArgq2JF)%d^5z?*LBL_lMlP;4d)vQ^fp zdFYpWu2et-6w%Zf8p_dyqF}S(ps8us)ub>BwNHPFO8v3}RlzpAe?G7bg}{JX(sex; z3Sp;S!PYeh37h=^wL4yQetJSVpmYrl?mO76;7k?{LXO7w+gpMTux9pgrebK8W_V)k z%*f!8cQ|t!&$MBf;|vtZ zBkCGW-#yEI5Xx9kRb~P!IGF0Elcq#C6W#IF8UGxeUI-}Wu?37G;U(n?28)#dDjM~_ z{Aqb!j!vAdB?LAlDD?kxGs1%YGl!P{m$_NmdHKRq657*+6Qt}M@-IM~&g3JO>Z^#E zJkKye2<0pwMospt?w7-2$9r z@rM@3KPE1J-1&8BCQoGZ2dC_4g98kKeqqOqQ*;_nG@<6xDNNkNX#~s4*3k?y=LEHI z^~=b$J3pO=14!v|sP41LlX2j^U>+x@Fz+zV7ex`Tglr=Pm~EwWF^?WqY0V#}j#!xK zYaA+iTR-oecC(|Alv+QaxsX0-og|84mX~|vNT1vFAEk3xq3H4cYzPN{wkWBa;HdaU zZD*>sN2~35Fqt?!nK*P|z4qeW^BXRA%GHcxMJZQ@=IThgI&D5(nyc$U-G{FIxyN)M zm`kknNYRvZ-l&~M&lxR*!0ew$>C}#GucOOI;y)-+i)91Ax2Tovx?`K960~#1k=z%_ z&34@O1#+_;lK@o}PCX@Zj{hR3qqCm3*;Il8>Bkv|P3RR_AkZW0&*zqDcp{UVh-Did zjWf6d?3c^9D4x~*d8+uC9C2p?Cg*M{u?Rm8$PX5FTar+4Wfc=+tXNp`(BFPG1U`o_ z$cvndK%sFC9-ZDVh(abM+Dv-bLFJ-Q{Rs#N?4|3Qi;fbd(s<-$$0jkajiR zcYG~=_blT$isn*Mt{%XN%jwuJ&BAmAEA>Norz=8yzNPzq*Quu9&jua+ zguXGt$VWr|mAM7~@${h=ZBL%kOAWXP=xD$n<)==1w3D9H$=9@#uO)m7iRGZ?3$CAh z;}?=N=6J=K2&-^K923sJ(qHUrO8=U2K9i)&&KY{WR%TLX_p}C;C0P;ojfn$1%!r?6rKcuA#|#HM6E>L zQ2hwRXbKp)HA7}L)KgTdA$rgvJkys`edL;;2k987JMh2}XnRa%85Q%S6B%%mtyCSjwX(6bhX zCS$sij}2DtjRgPC>G6jIFP|ye<=+h4i8Ab`U3ONaCMjZo9<=V3E@kR|y6&I)pUB5e zT8q9Y&5C`@FajwmvKg-rHX#CrUkOf>uNv+eYtI22wooCR0{4(v<2Of*tdgKYKMT4JvL%~A}dp}M*1!x91M z9l&A)pzKdqz_{yL2#TKEzl?k6=1*9(;dBXfCy_m~U|Sfu=_6Ijl6f}yBDb0#JpE^S z{6{>Zq~!6seE{U{llt-d#;v5`^q%MAZN(5=ql;Km`dgR=DwLWA5{&~NIUO|-$4B{& z#;f$KXU_&N^SSQ$t7=z6$=!-i8l@d|_p09AeYcRAtg97AcpG`ET@DU_7lzI^ zl5#h&T)FFA`ECKs=IRys=9zC@{KiEDF&|3&&1>I!^&77;HBci?qVZ5tKFr#SK?a_ z<=n>{6g7NJu^#YVcD^CpFk7V1 zjrTM(X>EQ4_5JqyUr2Qx(K?T;cOF}BJ)V?L7)af#)$e6kK>c_@2ejq^kinC>=KCe< zbv<`s+Xecsq&l1EIGB_VS*81Rw9b>J{`IE87a}Gb(J`od4kkJX`PoEfE7OqHOl16+ zx)^Jo5K8?kZRlOC@2(gP!4UB#wNGQm{m6Rb0jgcL8Z+9t+iGVNl@)6t)ij_r5&BR# zWzT!JDQRw(E}|}7gf8utia~!m!FOwN_xDkNgo*}I%6LmL1Aiq-7HSBq>?=Fy=_raDrp42uanr&*_M4E|qFrQag zJfh0`U?qc3ls$>gv2|sf5Vf0Ch7hTwCOZ1!%IPy$MMvd7mcldnQ%w2Z>aHMD|L>HtIU8HBm)av4~ES zvVuZojVd%0R5sq5S+8t0hB%_I91eWv*td@{-p)b3l9anF z#1hZ`>%zR^;h(!|imyAK4Q2HS9V4Ge(*9SRcoxq(8M3IjI+>*8{hGWVK_f4*2rugq zbdv)#gr($uP3}(&oJq=OX&6VBvn(R$sT0GeV3pu;Y2{aseDTPakA3rOs|HPI z#pD(`Z>~OlCPV8!{6`uYG?V=J3MS%qTWw;^ay}nqI%6b?XZ<{|06>2kI%A@xFZ*e6 zQ!9iomfBT`Q4W&kvwlK-HPgBPP7^||(tqOgWco=vwpT(7k#F-5wx6*qKbq)-L4gp!#%?COK)4cT{cc&(i79u*}T`; zxmU5U*W1s`ALNMPeJxCyCL(&apGr6RlbMQiIYed32PeXx#R(8^8=f+o%ZXgGhj}?I zecdpfgcp_6c3vIl|9A70*4t}43yl~@tPt+v^#aY83TfVs4XbSCd1Q9lUQ{;wnb1;U z*=(g3V4}i?2`e``JuJ%2cG4Cxj`7-AZ@acFVhGHt*<;L(@su1t80%WNANb2ScU$l2 z)0UgNX3ub!)emRSb3ZX0E2cPn`T|dHy=O0S_qM4o^O)A#u2YQD@gh31x28CT5GS50 z8;=mvm)W+E1}yqAPs9W7e6J7NhC)3D5j~jtqo8YXDvwov?(&$LoUAv9@F0Y>EzW0n z4%So31HtOVR1BYw8HA0%%x?k<`?t0FCwd+&#t6E#XGPN_q zb>2E*No^yvw^rD%>^-k`>wblLb#UBUZ?Dn)^N+FW8k zi!L%oT>%^`<|g^9aG2J6Hp1PrtENlkF3)a$b>FtI*LWK1Z9#;At)^KMD!!Wc+vg#- zcp8r)F1e>A16FGmtO8!jaQ!ANWOBhxTIeV*0rIDFy;(nD3&nXP3pW`pB!FYZ+$5i^ zTjSS>n+6p+dSM>cDr0EfMdnnBp(s~|9iRlY?AsP@|e~;!}z#`*-kM8PnSu_jp7(W zy+~84xAH~m-?YBC%T|9eCo}o!CjEpq%-Ij{?{c^G7Pixuc(|2&Mn4h2v0`qL&({6q zD?G>9f6QHw;td+z7WUV88tXm#E_cuV6Yk=0_#0!e7$>6s*KNT;|J%D?-2ZyS5Iw!K zW(|?L&#EPZWpTsZoAgGTEdh-0B6-|CixA5Vw%Lh5ZfNp02}k3t<%VW&^K;4#&7VSU zP;7ETKX0{gi?>BsQoB^32;~M-ryeb@Y%Mp~j4FlQUDM2sd*Z`OO0dDqtK zh2lEc_4=EIvW?y1K$DPGOsfUJgP2xqRwx9t(&lXw;$drAY4^52C#|%93R>x~(aNCN zVtc%MgeA4hm>oh|v1_S!d~_HVm`*uTvV;?{eB{oCvynk$5ylCW=Vto@=+{htY= zwH8e7;=Gf=7Og=a%E(>R8-?c%Y^j_=dNXBMrn+@_idm4h-&Di3oppU)_1~bxDowQ2 zAL9{s=^C-@@Mtt%IcDBvqUc`!9{_0?3MV!lx&Liig3qO{&ozQ<3N4~5nCTR)9`*04 zDxp<_jN9n5ug#`tmd?zK|6e<^Th23&3*bXzxP(#Ea>fmnFh><(U{?F*#M!jSqd1t3 zTP2>m=$q-X+LciE=VZ?iteYlKjVq*c;yiZ|H#dG@$FYBq{xWgAc@|Lh?4zUaCLVh2 z?K+ApM>B&3EzC_?Y*DeNF5*c4ErXb@@;gxlAO#FSbiu0tyE(U=QXHLv^qX9BN=Xq6 z-8Maj5QIo!F6+8i0%RdNUGAYPte{`3Umy}6w3{^`z5cDt8jyx{ymE%_If*PP9uvrf z9SBt=6NqsEr>UsTuIRMap6aHaq%q>(Qj7Rb1s2ixQFc`UEcQUBgWWYinbPeA(HJc3 zC`&;VHXM{Wm2ThcFEK6}2;EU4VLZ+SmzHn&?{J-{D$4H@_6QUHOj+@xx$QUMbFV!Xh zypL?c)iasSexN6#_YnkBqjQ+<)ZYB6IKy^EPS3H3ouJvIhv#5aTz1L1Vw*r&fa8^C z0}&X}fQ2tu?83!qp(s-wOgsAGPGu!tj9X>HF!ccgE{A6#?0Q?+UV^o*tqAxM?!zEk zBy)k-ExJ1-UZkurvmeIZ-peQB`M646iMuEKsv7h!1&F>`CfC@-YXn{I4ax#61?9Rk;~J-Uf8s=?g<$;1BwpcGNMc z*U@F=P&)397o+d{q3blNd;)Po`tidxH(s8bFqAe1lVXNloVCw$PZ$7$Cu&ZT26UF^ z0#FT8U;zp0x-fpCGl{5PzM)}e)Mc=n7bXfsQR^X{KkN_PiJ~;Dcu~qv2OYRQ4-K`b z^5gIC8~O3W{*q}jIX0@S6|&M1O5;^FDb^s2JEQT^OOb`(A~+FxY`o$UN`poV8Deg{ z=qzJjqw(?yH4=@2PwCd4(s`GLM&hvTHGgw7?lX`MT;q~pst$&0{bCqfL-&_}ELejf zsS(A*HeDiPi%ZR>Gk>U;^~Xy*{#T zf*h9xa<5aTe!ZzAXLWdrW)m#Al=l23+e%I|#LQeV&C0*~nyw}KI*87uDoUEvxOAE> zaMUknq%D7y&K9Y3fRgD%ssrRIr^g^Y_R-@kJwBnw&+$mtFik%QU77bvs((Q-iYW%& zAqge}^BmKlk~nF;ryJpS&b-0f6uYlPFcRL;wLW*4zGES9tLWv$#dJNAT{9$eGgwcQ zt~5QyqEqO0fd%y?Dg#Y$b)8o_pr9+zo+ei#J!mV5skdI9BOOm>+iJ&!(=YoxGt=IQ z8SnII-^|NbPV2g{e@Kz5uZ>;wp7vx%I785A<4mPJQ}u7@aR-ldG0JS{(w3on=ocPU zQ5ILGFHfGHnDJe_JTW$N{_<3MmjOCEK&1`GbUuvw1@!BcN?;NxtgDj4k|LG%%GeaU z`VuCm^qc$CU6e+q0NFZ-J z`>vlqHFGXqo#RD$s5c0Ti;|qVe0k=|7-Ci!RpK5h?Kr1*!ioWB1MUb_#S&n*9y)9cS`TD#XNQT%$WD$OxmTha>FJ}3Bf*le)`nu zX|s5j#%4~Vju+EAG3~@%pL04(HBwYEEKFbaczom2V-x46&ZbLfm^6ZSQ}C(N6Bm3F zm!~GCPtTn8ot+*#b=o&R<~cu+t~XF1h{05-4e68Gff0+|SJL@(t5^4cu0dR4yPvd_ zby`DL*Tp(7*WAUO@vA-4Vxhyj`DLA{C%cv*W zYJ@2Te#xz#4Xh~W!aOfX2|Rcf#pKrEyHHtps~qwgf&V9CGV$ zWY){5Y!&vNK^`uXdg)7MluNEM%967O&#-)j4f_lL6}=qyq_L6T z00&zl#y2lGJj{ZVI};B1Fhy>I{;7nOnFP*?&uEziBDQaCEfz!n*=HQh~b%T}Ws(;)|)r?Yr;CUA9O#@T9K& z{%h-X!#3^OWXHkv4pQzj^=;puO?8ZF9iuV2rBO;?s_h{uw0FT?7fCJ+8#M{3_0Z6E2{F+Wnh z`<|K_n9v5GU4DxBF{R`lU7PcV4XMLZ+F@PO6D>nw&!n0*RG|h6_JdMGD;lU`qp=4{ zxQ|+TANbZ=jwML%8dW1*NxIJX1IGuz^fBf~dbAH(p~HxhHnhLjW2mO;xBAL-tFH{W zJEX?W_W%bv)%*1ukRW#h=JNP(dJFc*Q{%S zT}*k>ekCx#v&JVKNPwzogq?!*>Oq?>Wuj#E;`iO$)ie1m=36v2^``SqlIc)guzF1IjsNkVH%9?;~0}ogsbK1_oO*c(mCF`K7pdBDPob-OgXhFUM}uqF?uuL+?=>^ZQ$=a5L7I60)JHC2qMIg@>)@ybxH>m47^ zU|kZP>=^mM@=xTSwybx+Zp0-sj7Z{p z%MV_8_~s95KPdT8YoZ6{7S24{*?RxT!~EZ?_5r(1Xq)9-bt*+6Y*b5 zw0-{LJV(_R9Czi%#BEq7r#=CEwf6kW25PfxI}*u0TQ42d>UHxZXhBQwIoW$Od2Bi{ z^KxSL#(M9oN%=L_T6zS=8BZk-PbCgr{;8T6BAh*{M@KvQ!9wczjCOqHnN#YWp#FRc zR-2x|x{#AuZFO`3vUUS~9u*!S_HW*Om<^h4p2WGyY7h__!83&YE@90F! z?}Mmd-@^3M@1!RyAG`M4|J=huefuP zUXytj>D6{AkH};yFPluZ<7aiP@0H#yV`C6?W$?2Olb_Xhytmunb^5^gVD4e-!#QSR z0v(ySXI>W2W&7KQa>u*A_vjSw#xzqqCyzqa0Z zJwc^4a!It3;6S~tXT82RM}ki7A!$$8->4wTmLo~|sM*WR;htFO!3U>)_wsiyqX)H} zCMHmI#z{Sb*A9bF&sKxbV;YjE4$QEP-F(O6sv49NwviyT6FJ@^^E%WPUGH6DRjbo& z_+X;od^q+HCSYKP1%7N01~GI|liygc8?w>)b4u-i!sJPax%&myG?Fd**IURu5F6GA zLGL%DS`KI}2Yy(ZI&@w;q>JSo$|O33DU~OhX>33>bRm_o0VeHKfvvKyAx$3o!BA@d zS#AH>qF6C0Aq zT%!s76$YIaIkSGE<6N^ck2GtV)8L0>=!#|&DrUGiVk9^+!^>yJMD3bv2hX-n#ShM~ zTJ7LvvEFmdtTuuW(b1snvMl_296P1eK=$*RM~iTXj@D-l-)Rx_Iv#Glp=^&;O!_I* zexg)lUq0*SDS%-P+LXZ$hs{iNSuq5j12YkVx@Z49Mf#tm_CMRg` zb%RNc4xY|>BYms&#a+Bio7TFM$F$zOC2-s+Ph2X|!80q>CTV)C9JtF$vy|5&&APdN zk>p3ORB$zW*x4+dbxiGBl#b;!ON)+fIlh}+Of+UL-5dK$#(hNT{3)1HG&T^MPEN%S z`WP3<>mze*xJN4Jlhfo{sUk>_(L>4JMIOWh-{g-6uXHkw(Z#RGcDAo zi;-esVmTOE(5+MUIFRHvg%m7?BK}yqgx#lbT2&*e`YKBij9v>ygV<1D#xm6>kmc)zH~V*N(kbfqJVCCv%5fFg)KK% z{obOR9Wocp0WG$i>BSZ^(}ns=1i2Anl z`1pA0_*MM7SCy*k)#`dvb%(UNL&>_)#PwJ2mOpN6rXjAhB30Ume;Z1BO4+9=`%=mg zO*!(z^U1R})|FQiuH8SYP!b&nQXNOMjw7j#V_L_tXU@FJ-bY=qt92sPe@g2=wcdYb zz3XhEs^gg~!Tfk>mqz~vKgdrV@@j{?sY5>PkS}$}uO0F~>fQhF)W5oP_f*o=&CCRM zYp(8;>yYL;lsNom(xuuw2Q}B=!<`T3lde%3BCAp@$2C~%sy=b|%!X@+Zr_|Opp891 z%6(6I$A5G@u}HRRmmH_aS}MI@Ew%5o)5s5r8Z{RzVWoDDYP(01t|O1hT)JL{e$CbY zp=;3QLj_2>hHOQma(?I<{hLqPq}tYxB}ZlNMi&e$c09|2^OsR#Z$Ea{ojE4`{xR3t zvb_J`a^N{{a54l+5{)s$TR#3V9@~z3IEas4jhNZLtwud_RKY>fSt29$BZJ1ArM%z4k3~k$1X!jlr^ZdsxrucCrqo(qR=V@i;nB;;06l_; zy$k;y%%u7zwZ6$z-;CA=_cg8W+M|k^cVda%2U8V?w2DK_RCS-`?n}8RH21`hrap9g zVD-y=7?1wzjznn}lV`eHS+Vu1T&G&>rTY9=avdhuEV)2}l6sOpkI>@}=#!Y5dVoIJ zuH&E3=j-&SA-}&Q*U#wj8a@6yJ(Bbw(-UkaIW>>rEv6mh+FsNR7yh#t*eq>vRD%*IRqfg>GDq&ge#b>(A=bI1tqfy+k z5%T%E9qI~vqX?FCL#`_5Rbr3#Dc}$ufAgEt&z<`V4t(^6qp+a%BX<#;I~ofn93LH> zDJZD?C{!|ASkU)T+|^KUv$%{G5Htl4Iykr7N0r<%v}F zClY35PRFiy?tCKQ^-;+chokJHMQ5c${-~?kQT@rF+fnh!o;ziZ8OJ+CKbM$;mEwN^ DaHfjo literal 16236 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

$;tx&8-`d-@CB$W;6GR*a18V2<^Rte^Pq z0Lwx*q|IcO?S1U62IgcVe1`1Vcc2P7ARWFpy~Eb!(ebETWV?a486S*20FE|7@?>Wq z*x#Q6^I6E$9noh#(3&_*!FB~+25)M+D&JOZrmFLtT)+tZKBDQchj=$DHIx4GfV7K^#|kKC(e=AJzUW8&gMT7Z{hS?X3w@?5?Z z1gFuXy>n?Pe%NUOQCv#b3F2jm5R6Vm84K*9Lo-y$F(rz(S geOY0J$m=(_zPxI18rWw|7fptq4eK8me3HTc7e_~gA^-pY diff --git a/src/osbridge/__pycache__/input_dock.cpython-314.pyc b/src/osbridge/__pycache__/input_dock.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fa215b6a67646ddb047d74218eb06995857aab15 GIT binary patch literal 43711 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 bauXMqE!QMJ7neQP7k&iSvUY?RQ=I<=?fH-2 literal 0 HcmV?d00001 diff --git a/src/osbridge/resources/resources.qrc b/src/osbridge/resources/resources.qrc index e69de29b..9c4f7bb3 100644 --- a/src/osbridge/resources/resources.qrc +++ b/src/osbridge/resources/resources.qrc @@ -0,0 +1,14 @@ + + + vectors/arrow_down_light.svg + vectors/arrow_down_dark.svg + vectors/arrow_up_light.svg + vectors/arrow_up_dark.svg + themes/lightstyle.qss + vectors/design.svg + vectors/save.svg + vectors/design_report.svg + vectors/lock_open.svg + vectors/lock_close.svg + + diff --git a/src/osbridge/resources/resources_rc.py b/src/osbridge/resources/resources_rc.py new file mode 100644 index 00000000..3199f23c --- /dev/null +++ b/src/osbridge/resources/resources_rc.py @@ -0,0 +1,941 @@ +# Resource object code (Python 3) +# Created by: object code +# Created by: The Resource Compiler for Qt version 6.9.2 +# WARNING! All changes made in this file will be lost! + +from PySide6 import QtCore + +qt_resource_data = b"\ +\x00\x00\x16H\ +\x00\ +\x00\x98\x93x\xda\xe5=ko\xdb\xb8\xb2\xdf\xf3+\x88\ +\xe6\xc3\xb6\x8b$\x95\xdf\x8e\x8b\xbd\x80\x938\xd9\xe0\xa4\ +I\x9b\xb8g\xb1\xb7\xb80d\x8b\xb6\xb5U,\x1dI\ +N\x9aS\xf4\xbf\x1f\x92\x22%R\xe2Kr\xdam\xcf\ +\x8d\xd04\x96)\xcep8\x9c\x17g\xa8\xd7\xbf\x82\xdf\ +j\xfd\xec\x01\x00n\xee\xce\xc6\x17\xe0\xe2\xc3%\xb8\x9b\ +\xfey5\xb9\xfb}2\x99\x82Cps{1\xbe\xbe\ +\xfc\xdf\xc9\x198\xf9\x13\xdc\xbd\x9b\x9c^\x9e_\x9e^\ +N\xff\xc4\x8f\xd4\x03\x02~}\xbd\xb7\xf7\xba\x09j\xad\ +#pqus2\xbe\xa2\xa8\x81\x97W\xd0MRp\ +\x17\xc1\x85\xbf\xf4\x17\xaf\x9a!\xf3+\xf8\x82\x1f\x04\xcb\ +p\x93\x1e.\xdd{?x\x1a\x81\x17\x1f\xe6\xdbM\xba\ +\x05w\xee&y\xf1f\xef\xeb\xde\xfb\xb7\xae\xbf\xf9\xc3\ +\xdfx\xe1#m?w\x17\x9fVq\xb8\xddx\x87\x8b\ +0\x08\xe3\x11\xd8_v\xf1\xf5&\xfb:\x8c=\x88n\ +\xb6\xa2\xcf \x09\x03\xdf\x03\xfb\xc7\x8e\xbblu\xb2\xaf\ +\xef\xddx\xe5oF\xc0\x89>g7\x22\xd7\xf3\xfc\xcd\ +\x8a\xde\xf9\xba\xb7\xf7~\x1a\x86\xc1\xd4\x8f\xd4\xe0\xce\xc9\ +O\xf68\xbb\xe7\x90\x1f\x0d\x0a\xe3s\x86B\x0e\xb1\x8d\ +\xbeo3<\x08\x19\x12\xff\xdf\x10=\x97\xdf\xccz:\ +\x8c]\xcf\xdf&\x1c\xd2\xff\x8a\xe20\x82q\xfat\xe8\ +\x06\xfejs\x0f7\xe9\x08\x8c\xf1\x9f\xff\ +\xd9\xa6i\xb8Q.\xa7\xc7\xb5\x9fBA<\xcc\x03\xd4\ +\x88[\xe7\x8f\x10s\x1a\xba\x1f\x06\xde\x1b #HO\ +\x14\x01\xbc0\xe1:\xcbE\x09j\x0eZ]\xf6L\x0a\ +?\xa7\x99d\x18\x81E.\x0fx\xdcGk<\x91j\ +\x81\xc0\xcb+\x838\x13\x07]\x02\x13\xc50I\xa0G\ +\x01U\xa9\xa1%\x9er\xe8M\xc5T\xf7(\xd7\xa1e\ +\xc6\xb8A\xe4\x88}\x0f\x82\x0b\x88\x96\xa0\xdb\x94=0\ +^T[\x9d\xa2\x89Fk\x04d\x94HJ\xcc\xb3\xff\ +HZ\x91U\x8fZ\xcd\xe6\xe4\xee\x81\xd0d\x11\x84\x09\ +\xa4\xdf\xd8)?\x15\x0d\xab\x1a\xa4\xba\x22\xab\x8a\xd0\x84\ +\xad\x89\x89\xbcco\xe0\x0d,\xfb\x129E\xd2\xdbb\ +\x89\xafJo<\x91L\x08M\x86\xadV\xbb\xa3\xef\xc2\ +\x88\xc7yk\xe0\x0c\xc6\x8c\x09\xa7k\x88\x84\xf34\x5c\ +\xad\x90~\xa5r\xa14\xd3)nB[<\x87\xacl\ +\xcb&\x88\x03b\xa0B\xbc\x9a\xbb/\xdb\xbd\xde\x01(\ +~\xb5\x1c\xe7\x15\x1b\xd1\xb5\xfb0wc\xc5X6\xe4\ +K3O\x92\x1f\xb5\x85\x94\xf3\x1a\x12Y\xe4\xdf0\xfb\ +'\x11\x91\x9b0\xbew\x83\xba\xccL[\x22\x9d-\xc8\ +-\x1e/\xdad\x1e\xa2\xa1\xdc+[\xf1\xa24\x80\xcb\ +\xb4Bx\x81\x22\x06\xd2W\xe6Z-n+\xa8K\x9a\ +HPg\xadtH~LR7\x85\xbf\xbd\xc0\xe6\xc5\ +\x03|\xf1\x7fvZ@6\xb5\x0d\x84\x8bt\xc2\x9fe\ +\xc8\xca\xd9B<\xfd\x16\x19X\x0a\x8e&\xb6\x97\xc0\xcf\ +\xbc%\x9e\xabTd\xc1\xa5k|#\x1fdCe\xd6\ +.0\x9f\xf4\xf0\xf5F\xab\xfes\xc2\x0d3\xc2\x89F\ +R\x8by\x10\xa5\xf9\xe6\x06%\xb2d\x15\x09\x05\xc7\xf0\ +=\x18E\xa2\xccZh\xd7\xb7\x16x\xa0\x1f\x13\x18\xc0\ +E\x0a\xbd\xdf^\xa4\xf1\xb6\xe0R\x09a\x1b\xe3\x93\xf1\ +\xc6\x19r\x05\xc0\xe96A|\xa5`\x11\xec,\xcc\x16\ +\xa4\x85Q\xf2\xc9@;\x0aw\xae:\xc9-\xc7\xde\x84\ +\x93\xa0e\x9e\xa9\x81w\ +!\xbf\x1a}\xb01\x90\xb4\x22D\x06\xe6\xeeaE\x9d\ +m\x0a\xca_H\x96p\xd5\xa1nD\xf3\xfe\x11\x98\x8e\ +O\xc0\xc9\xf8\x96R\xbb\xa1\x9f9u\xe7'n\xbc\x7f\ +\xef\xfa\x9bY\xea\xce\x93\xd1\x08\xfd\x96 \xbd\x7f\xde\xc5\ +\x97\xc0\x8eX\xf5\x09\x0b\x88Y\xe2\xfcb\xcc\x96\x85\xbe\ +QY#K`I42\xdf*_\xae}\xec\xa1b\ +%\xc6\xfeP[\x87\xfcbn\x89*\x8f\x0e\xad\x88\xd5\ +\xc9\xa84b\xbaCF.\x93ij\xf0v\xed\xacF\ +\xdd\xa8\xd5hW5\xb5\x8dIT\x99lI\x9b\xead\ +\xf3j\xbf\x8a\x0d\xf1\x88\x0e\x05]\xe7\xdf\xbb+4!\ +\xdb8x9z\xfd\x80\xc8\x1b\xc6\xc9k\xe6\xca\x11\x07\ +*\xc0@\x8e\x92\x87\xd5\xab\x0cv\xb2\x9dS\x1f\xef0\ +D\x08`{\x85\xd2\xa5\xf2}\x14&>\x8eu1]\ +\x07\x08\xc26\xd8\x09D3\xe3HZS\x1c\x1b\x06\x97\ +\x06G\xe0\xfcv\xfcv\xb2\xdb\xf2\xa6\x02\xe9\x84\xf0\xd1\ +\x95\xbf\x81:\x93\x22\x0b_cbd\x81Aj\xce\xaf\ +\xa1\xebi\x15\x15\xe5\xc8\xe2A\x1a\xbd\x93G\x14\xed\x8c\ +V\xd8\x86\xc3\xa5#\xd7\xeb=\xc6\xdf\x19\xb4\xbb,\x08\ +)rt\xd5\x08\x90\xba\xa8\xa2\x87\xfa\xcahCW\xa0\ +2/\xc7\x83K\x17i\xdd\xc2\xcd\xf9{\xd0`\x22I\ +\xe3nap\xadn\x17y\xe3\x03\xec\x92\x1f\xbf\xb22\ +\xa8\xa5\xe3Q\xa2A\xf8\xff{\xe3 \x9a?\x88\xb9\xee\ +%\x82Y\x1b\xf7S\x07\xb4ZU#\x1a\x1b\x86f\x14\ +\xa4~\x11/ \xa9)WQ g\xbdI\xf7\xf8\xa4\ +\xb9\x810<\x02W\xe3\x93\xc9\xd5n\xf2#3\x9d\xd0\ +\x08\x12\xbfd;\xd1\x99\x5c\xc5\xeeSF\x81\xac\xe9}\ +\xe8m\x038K \xd9T\x90=\xa1\xd4\xc1}\xed\x12\ +\xa9\x06\x93z\xce+\xc9N\x97\xf3\xac\xde~10\xb6\ +\xd5\x91\xfai\x1e`\xab\x06\xd9\xcdf%?\xe2N\xc9\ +\xea \xb8\xe6\xd2-\x83\x8bz\x81\xf1\xcc\x83\x0f\xf5i\ +YE>\x8c\xa0\xca\x026\xed*\x00\x9bm\x052\x00\ +n\xf5\x0e\xe6n\xaf\xdd\x13\xdbd\xd4&\xf6\x84\xb8\x94\ +e\xad\x88\x82\x96HQ1\x8a\x5c\xd9q\xd5\x8d\x5c)\ +\xb1\xff\xff\x91\xc2\xe0\x82f\xe3\x07\xca\x10\xe2\x7f\x113\ +X\xe8M\x095\xec\xf7\xdb~ Z\x14\x01\x99\x9b\xcc\ +L\x03\x848\x09'\xef\xa3,D\x93\xfc\xce[~\xb2\ + \x8a4\x9b@!\x15\x85\x10#'\xec\xed\x0d\xa5\xfd\ +\xe5`\xe9.\x17&\xd9\xcdL\xc7\xb2R\xfa\x19\x86S\ +\xd6V\x123\xd8j\xc4\x9b0\x8b\xb6%\xa5\xd1\x0eZ\ +C\xe7\xb8\xaf\xd1D\x85.\xed\x8ak'\x8a\x91\xdb\x13\ +?M\xe1\xe7\xb4\xd4g\xcbm;\xed\x85\xa6\xcfj\x80\ +\xcc\x98c@\xd7+D\x9e\x9b'\x87\xaa\x1a\x89\x84\xe0\ +\xca\xfe=\xb4\xf6%]\xbb\x8e\x0b\x17NM]\xde\x12\ +\xc9\x85c?W\x12\x85\xadb\xb7\xbe\x11m\x05/6\ +\xb5\x0f\x8f\x8f\xc0\xdd\xe9\xed\xcd\xd5\xd5\xce!$\xab\xec\ +\x0d\xda\x0a\xb9\xd8#\xf4e\xea/\xdc\xa0d\x14s\xbb\ +jB \x85[$twDj6\x0dK7\xd92\ +\xc9\x83!u\xd1\xa4\xd9L\x15ty\xe4\x16s\xaf\x07\ +\xe5^j\x91i\x82\x90Y\xd3)+\xf6Tw\xc5F\ +\xa1\xba9\xeem\x04\x05\xad\xfe\xc3\xc0\xdf\x14p\x0e@\ +\x83^\x92\xed\x5c\xec\xc5j\xaaY:PC\xb4#\x1c\ +\x94\xd9\x19m\xa1\x17\x09y\xa5H\xc6\x10\xc7\x93\x92\x9d\ +\xd2\xa0$\x1b4TY\x93N\xf1^\xb9\x9f\x223e\ +Y^s\x12\xe0;\xac6\x07_\xc2j\x1bJ\x0c\x09\ +\xfe\x1f\xc3\xf3\xf7,\xf9\xaf\x1evV\x8b\xcc\xc1\x97t\ +\x91u\xb5\x8b\xac@*\xdc\x80l\xbd\xec\x86\x9dv\xd1\ +\xa1\xab\x04\xf5q\x0d7\x80\xed\xbe\xec\x08Y\xb5\x89\x83\ +`\x0f\x1d|\xbd\x01\x08\xf0\xe4\x01AD\x0a\xf3\x13\xc2\ +r\x15\xc3\xa7\x0a\x0a\x19~\xb7\xf0\x1e\x0d\x04 n{\ +\x8dx\x1e\xe0\xa5\x0a\xe64\x7f\xe8\xa5\x1b\xc7\xe1c\xf2\ +\xaa.\xc2\x12\xc9\xf1\x8c\x22\x01\xaf\x83\xf4\x09u\xb0\x02\ +\xe9\x1a\x02\xbcJ\x01A\xe1%\xfe\x88\x96\xd6\xe2\x13\xfa\ +\x8c\x1f\x22\xdfg\xd4k4\x86\x92\x18i$\x1f\xf0T\ +\xbcu?\xc1\x12\xae\x9c\x048\x00\x01LS6\x9c\x02\ +\x08\xd7\x17H\xd6\xe1#\xfa\x1a}X\xad\x8b\xb9;\xcb\ +\xdcV:_ AT\x81\xe5M\xb0i\x18)r\x07\ +\xb3p\xad]Z\x05g`\xf2a\x06.\xf4P5c\ +\xeao5\xe3\xe5BV\x95\xd5\x80>\x92\x15X\xda\xac\ +\x17\x86gH)\x1c\x8eO\xda\x9dq%\xd7\xe4\x8d:\ +%Cn\xbd\xd6\x1a\xe0;\xba\x00\xed\x86H\x97\xabn\ +\x90\xfc(~\x94A\x9e\xc5a\xe4\x85\x8f\x1b\x80\xd3+\ +\xb81\x92\xa4\x183\x1f*\x83tR|\xabY\x84\x18\ +\xcch\xc4E}\x15\x0e\x1a\xa3G\xa7\xd3Q\xa9a.\ +\xed\xa5\xdc\xb7v\xcfO\x91\xc7\xa46\x0fq\xdfB\x0a\ +\xfa)K\xce\xa6\xdd\xab6GZ\xb0\x07\x8f\xdf\x94\x9a\ +\x14}\xe7d\xd2D\xbf\xcbZ\xb3;\xe4\x1f\xe2\xe9\xcb\ +Lh\x05\xaa\x1f\x97\xe1b+\xe1W1e\xe1tx\ +6\x1c\xb7I7x\x1bj\xe2\xf9\xac\xa3K\x9c>o\ +\xa5\x0d\xb4\xfe\x97|\x82\x15\xe0F\x04g\x0a4\xdc\xa6\ +D3U\xf2\xfa\xb8\x0cu\xea\x8f\xae\xc38]l\xd3\ +\x7f\xc0\xa7\x03\xb0\x1f\xa0\xff\xa4\x8c\xdd\xc3\x97\x92\xb7=\ +\xcf\xd3\xdb2l\x00\xfd~_\xb3QmX\xb1m\x96\ +\xc7\x95#\x1f\x05\xdbD\xe6\x91\x0e\x87\xd6\xd1\x8f\x1eK\ +n\xc9v;h\x98\xe8\x12\xad\x0b\x19\x19\x86\xcb\xe3\xa5\ +\xab$\x03\x1f\xd9`\x0b\x8e\x22Mv\xb1\xbb\xaa\x88\x0a\ +]\xf1U\x1c2\xeb\xec\x8bz+E\x9bIS\xedO\ +\x95\x1b\xd6\xa0o!\xacv\x8d\xe0\xd4\x88\xa0\x14\xdc\xb7\ +\x9dg\x81,\xdc\x01vp\x8a \x86l^U\x01\x92\ +R\xa0\x02\xd9D\x08m\x92\xab\x22\xec\x0c(\xc2^&\ +\x94\xfb\x924\xc1\xcc\xec\xb2N\xe6\x02\xb5\xb2\xb9\x80!\ +\x9d\x0b\xc8\xf3\xb9\x804\xa1\x0b\xc8\xd7\x16 \xb3\xa0\x1e\ +\x91]*\x17\xd0p\x89\x11\x82e\x12\x17\xe0\xd7g\xc6\ +-\x97\xf2\x8d\xd0\xef\xb4>\x0b\x1c\xd4\xceS\x9d5T\ +\xac\x00\xd0p\x0d\xbd%=\x90\x8f\x07`\xd75\xc4\xb6\ +}\xc9n\xa7\xaa\x82J\x92\xa4\xa8T\xe7ej\x0eE\ +\xf43@8\x90\x8d}\x07Ye\xe3)2\xce\xe6\xb1\ +\xff\xe2\x00$H[\x1e&0\xf6%\x86\xdf\xd0\x10\xdd\ +\x96\xa6,\xe9T1om\x09\xc6\x02\xf1l\xcavM\ +\xcd\xb8\x08\xcf\x7fm\xca\x7fm-\x9c\x19\xbc\x8f\xd2\xa7\ +gd{\x01\xac\x8e\xed)>wQ\xe0&k\xe4\xd7\ +A\xb8\x99\xe1b\xc9\xd8\x0d\xb2\xaft9\xd2E\x01\xa9\ +\xb2N\xabX\xf9B\x1aRq?y\x9d\x10\xd8\xb3\xf9\ +\xea\xe8\xaf\x88eIq\xcf\xc50\x82.)y\xa0\x7f\ +VZ\x94\xf3\xa4x\x0e\xcc\xfa\x0eB\xc2\x81\x82\xc60\ +l\xdd\xb3\x7f\xceQ\x87\x15\x81\xfc\xb6\xdbO\xc6\xa9\xae\ +\x07\x1e|\xf8\xc8\x02\x18{;vZr\xcbP\xf73\ +\xdc\xfd\x8c\xc5G\xbe\xecPP&\xf3\xbaUY\xfe\x85\ +\x05\xae\x09\xfd\xeb0\xfd\x86\xb5wZ\xb0&M\xa5%\ +\x94\x0cl^\x8d\xb73\xc3\xe0tbI\x85\xee\xcc\xdf\ +x\xfe\xc2M\xc3XV\xd4[|k\x1a\x92\x8e\xa7\xa7\ +H \x05H\xcb\x1c\xbeC\x8b\xd6\x12\xc5\x94>3#\ +!$\xab\x14xc\xdd9\xeb;/\x8bK7\xb4l\ +XS\x8a&K\xb4e\xfda\xdf\x18'N\x0a\xc8f\ +\xa5\xc68fm\xd5\xa9\xb6v\xcb\x0e\x90\xdc\xf5'\xf5\ +\xaa\x8e\xd5\xd6\x9a,\x1af\x09U\x1b\x14`U\xfc\xb6\ +}i\x02\xbd\x93!\xbel\xa8.\x0f\xbaHO\x16\x90\ +\xacAV\xa6\xafsR\xeb\xf2\x81&>\xd3\xab\x95\xa4\ +]\x0b\x94zf\x8ab\xcd\xfe\xb9#\xd2\x22\x1fl\xa7\ +\x16\xbc\x04\x22v\xe2\xc4\x04\x0b\xac\xb4d\xec\xb7\x8f\x80\ +\xa2K\x92s^\xaaPg9\xd5m\xd9\x9e\xa7\xa3\xd8\ +\xf3\xac7'Y*\x09\x09\xfbS\xd4\xcb;?l(\ +%\xdb.%u\xa0\xb3$\x8du\xc7X\x1cw\xe7\xc3\ +V\xbf\xac6\xf8g\x8d\xd5N\xfd\xc5\xb0\xeb\x0cU\xb5\ +\xc7\x8d\xb26\x14E\x84F$MZ\xb5\x07\x07]g\ + \x84\xf6\xb0\x86\xa4\xa5\x22\x19;\xe6,\x22,\x01 \ +\xfe\xf0\xb2Z\xd2\xc1\xfb\xd35\x5c|:\x09?k\x12\ +\x02\x992(\x15\xf9\x1f\x92\xd8\xec([\xed\xc2\x17\xac\ +\xfa\xb0\xd4^VVj\x89\xdd\xc8\xf3\x13w\x1e\x94K\ +\xf3\xb3=\xab\xfe\x8f\x83\xa6\xa4&\xa2\x22\xf2\x9e\x15-\ +K\xbcFe\xe3\x83\x01i\x97\xd7f\xab\x1cQ\xb6\xed\ +x\xb4\xc0\xf7`)\x7f\xbe\xe1(\xff\x85#\xa9h>\ +1\xf1\xd0H_\xe6\x8eA\x82\xf4\x9a\xeb\x8d\x10\xc7\x1f\ +\x80\xcf\xad\x91s\xd4;\x00O\xadQ\x0b}j\xd3O\ +\xe8\xff\x83\xa2=\x16pN\xe6=\xb4\xda\xce\x01(~\ +\xe1\x0c\xf9R\xc3\x16m8\xc4m\xf2_\xb8!i\xf7\ +J\x95\x82\xf5\xf7\xa2\xdbv\xf0\xd7\xc5/\x0e\xdd&\xf3\ +\xb8\xdd\xfc\xf83\xd9\xc7T\xc9\x7f5\x22\x8d\xb1\x0a\x85\ +\xb7\xc1\x97\xc8Q\xc5\xdb\xac\x87Y-\x8b\x95\xf7G\xe9\ +\xce\x1e%&\xf2B\x19\xc0\xb05\xaa\xe4\xb5\xf7b\xb6\ +g\xaf\x9a\xbb^J\xf4,\xb7\x90\xe5\x8c*\xdaH:\ +\x12\xb5\x1d?\xe0FI\xd2\xd6\x85\xc8\xc2\xe1;\xe2F\ +\xa3\x1dZ\x16\xe9\xba\xfb^k\xd9\xea\xbbo\xacN\xbd\ +q\x9e\x0d3\xf3i\x07\xdf\x06/\xa5\xe3y\x15\xae\x92\ +C\x5c@k\xc7\xf5\x01jN\xdc^\x1a\x1a\xd5\xf0{\ +\x1b_\x16\x0e\x5c\xc7`\x93U\xec\xb7\xaf2lp\x9e\ +&\xde\xc6\xd3 4\xc4\x97\x95W#\x84L\x7f9\x0d\ +\xb7\xb1\x8f\x96\xf65|\xfc\xe5\x00\xdc\x87\x9b\x10)\xea\ +\x85\xce\xba4\xfb.\xd21\xa8\xb3\xb2\x047\xcf\xc1\x17\ +I\x22\xb9\xc2\xb4\xca2y\x96H\xec\xe3\x5c\x91$O\ +\x07\xcb\xb2^\xd0\xdc\xd9&ku\x14\xebRM\xf4z\ +\x89Zc\x07_Y\xf6\x0bD]\xdf\xab0_\xe7\xa9\ +b\xe5]\xe8\x8e\xf3\x8c8\xaaw\x1c\x06\x0e\xbe\x08\xa6\ +g\x5c\xb6T\x9e&\xc6P.\x105\x81n\x98\xfe\xc4\ +\xc6\x8d\x87\x8d\xb1\xf9\xdd\xf7`B@o\xa3\xd7$\x9b\ +\x22K\xc7\xb2E\xe1\xb9\xb2\x97\x0a`x?\x0f\x91\x0f\xf8\xa5\ +\x92\xf9S\x7f\x1b\xaa\xa7.SR\x1e3\xa9Ej4\ +\xf2b\xe4\xbd`\x15\xf6\xa5\xf91\x0d\xc8\x01bg4\ +T\x8e\xa2\xa0,\xa6\xc7\x01\x81\xcf\x22\x9b\x92\x93\x1b\xc8\ +\x87\x84h\xd9\x19iC\x8f\x96\x886l\xd3\x94y\xc8\ +\xbdJ\xb0\xa5'\x8f\xd2\xf6\x8c8\x81\xf7\xe3y\x82-\ +7\x92b\xf4O\x1f>\xee\xba\xa1XJ_\xab\x09=\ +\x8b\x93\xeftnf5m\xce\xa6\xa2^\x92\xb0\xd6\x10\ +y!\xef\xcb\xe0x[\x1d>g\xc3\xdfJd\x98k\ +\xfa\xa5>@\xc3\x00v\xc3G\x9e\x1d\xb7\x03V,\xac\ +\x0fdh\xb1\xa4\xcbr\xa0\x1bw0\xd0%\x9a\x13]\ +*e\x98~\x1d\x11U\xddC*\x1f\xf4(\xa5\xe6\x05\ +\xea6\xcaE\xaa]\x14\xa7Z\x8b\xc2\xef\xd0\x1c\x0d\xe1\ +\xbdf\x03_\x83\xc3hD\x8e\x1aPJOz<\xb5\ +Azf\xc7\x13\xe2&\xf4\x00\x1f\xc9\xce\xb0\x1c\xf5C\ +]\xed*w*\x96\xb0\x9f\x9e\xccp\xbbHv@5\ +kPG\xeb\xedU\x9fR\xf3V\xbb9ou\x9f\x9d\ +\xb7$x\xb3\x89\x05\xdf\x8f\xbb4XP\xf6\x02?8\ +\x7f\xc9\x86P9\x1b\x9bw\xda\xc0\xb1\xa6\xe2:\xcfu\ +\xae\x1f\xbc\x14J\xcd\x98a\xe0p\x01\xba\xcf\xf9\xdd\xb6\ +\xa33,\x8c\x07M\ +R1/%\x91b/Vr\x22\x8a)\x8b`\x90\x1f\ +,\x9eG9\x00E\x9f\x98{\x91\xbb1\x9fYRH\ +#\xc0\xc6\x97\x19f\xd6O\x9f\xf9n\x10\xae\xf6\xe9v\ +\xcb\xbd\x9b\xc2\x18\xdd\x99Ea\xb4\x8d\x9a\x1fC\xac\xd7\ +\xe9r`Y\xf4\xf5 G\x09G$q\xa8y\xe1\xe2\ +?\xd2\xa7\x99\x07\x11\xf7\x04\x89\xb2\xa1G>\x8aQ\xdc\ +j\x90R\x09]\x94\xb1\xcf3hM!\xa7\xe4\xfc\x81\ +\xa2\x80\xc1\x84\xe4G\xbc!u\xb3\x09\x9e\x94\x89\xfa|\ +&\xb6\xb3\xb4\x1a\xb9P\x22\xa2y\xdb\x87\xba'\x8b#\ +\xfc\x85\x94O\x19e\xc5\x13\xfeT\xa7Lt\xd4'\x22\ +\xcb\xf5\x82\x11cS\x9a\xc3\xc0=v[m\xcb\xbe\x8c\ +\xb9\xeb\xbd\xe5\xc0u\xaae\xd5T|\xadMq\xc0\xb5\ +2\x0ehQG\xf4\xd5\x00\x92w\xf3\xd7H7\xfe\x1b\ +\x8b\xed\xc0\x8c\x88\xf41S\x98OH\xb4)\xed\xfb\x88\ +5\xd7\xb5\x82dk}\x84\xab\xe1\xb0\xaa\x8f\xdb\xc4\xfa\ +\xd8\x19\x0e\xdfp\x14\xba`_\xcdNj\x87\xfb\xd6\xa6\ +\x80\x1bGlyD\xad\xe9l4\x81 &\x958\xb5\ +I\x9f\x87\xcdt0\xcb\x0dj\x8f\xaa.\x04]\xecM\ +\xb9S\x91\xe9\xcb\xc4\xb0Kj\xd4\xc6\xc5\xd0\xd4\x19\x87\ +\xdc\x82\x97l\x1b*\xf4x\xd1\xb1Z\xd7\x0b\xb5\x104\ +\xad\xb7\xd1\xeb\xa5\x0a4\xa6\xdb4\xc4b=\xc9>\x17\ +\xb0\xc7\xc9\xa7\xf7[\x98\x90W\x07qw\xe7hBo\ +\x12\xcf]\x95\xdb\x7f\x88p\x91M\xf9nv\xa8s\xf9\ +\xae\x07\x13\x7f\xb5\x99\xc50\x0a\xe34S(\x07%\xe3\ +\x0c\xd9\xd4\xde\x8cp\xa7\x8a\x1c\xbb&j|5\xcf\xb6\ +\xd5\x14\xebA\xd0)\xcb\xe80\xc5\x1e\x19>\x05\xe0\x0b\ +0\xbd\x86\xe5k\xcd\xb3]s\x80\xb4\x8c\x0aid\x9c\ +\x0b\x1a\xcc\x83\x96\xee}\x04\x86\xf2\xa0.\xf9\x91\xf7\xdb\ +\x96\xf4[\xa9\xe1\xeb\x91\x1f\xae\x03B\x82+\xc3I\x87\ +\x92,N\x8dz\xcf:\xbe\x0aW\xe1U\xcd\xb3\xbf\x05\ +S\xacB\x1f\xf6\x127\xeaQ\xbd\xf57>fK\x83\ +\xc9\xa7\x84%3\x9e\x1d\xd3\x1b\xda\xe4GHIS\xd9\ +5\xc8\x1a\xeb\x17[\xf82ub4\xf3\xdc>\xbe\xaa\ +\xdd\xb8\x9fo\x91 \x09\xe3\x9f\x84t%t\x9b\x12\xaf\ +\xdcMS\xf2\x9d\xe2\x13\xa7\x7f\x0a\xcaq\x98\x9a\x88\x06\ +\x07\xddEg!_\x8c\x9a^\xcdowr:\xc7\xed\ +\xb9\xe0\xec\x97UV\xaeFO\xb3\x00X\xf6I\xae\xc8\ +Lm\xcb\xbaJ\xde\xde\xe6hm5\xb681\xea\x04\ +\xe7b\x10\x93W\x8a$\xd7\xa4\xce\x9b4\xc4\xc2H\x07\ +_\xfap\x8e\x98V5\xc6v\xc3/\xda:\xd4a\xa4\ +*\x1fU\x0e\xb7\xf0+U\xa3\xb5\xf3\xbbU\xd5mV\ +\xef\xc6\xea\xc9\x12\xc1\x80&\xee\xa7\xae\xe0S\x9b<\xf2\ +\xb1\xaaM\xcf\x9f\xf7\x95\x816\xb3\x9d;u\x8697\ +F.\xc6\xc7\xfdV\xcb\x1a*\x95(\x16p\x8d\xb2\xa7\ +\x7f2\xec9\xe75&\xbd\xf9\xabaw\x9fJ\xb9\xbd\ +Ug2\x0d\x83+\xcf\xa7\x0d_\x7f\xcb\x92VK\xac\ ++\xfc`\x85\xf7\xb7z?\xa5\x16k\x16\xc9\xc4\xa9,\ +\xbav,\xb3V\x13Cn\xb8\xe36\xee\xe3\xeb\xfb\xec\ +\xb8\xe5\xfe\x10\x1d\x1cS\xbd.9\x87\x9e\x96\xb8\xbe\x9f\ +\xc6\x10J\xcb\xf0\xedv\xe1\xfa\xba\xd3|9\xfd\x89\x8f\ +n;\xf4\xf0\xc9*\x04\xfca\xbe\x09\x0fZ\xbc\xebf\ +\x89\xaa\xbcl\x92\x1e\xc4\xa1\xc6\xe7\x18_\xc6\xa3\x83\xa9\ +Zo\x88\x94E\x81\xa5w\x0c\x8f\x17\x03S\x96t=\ +\xa8&\xeb\x11.{\xb0\xdf\xac{E\xbdU\xb7\x92\x02\ +$\xeeV5\x80\xa0(\xd8\x11\xa6\x87\xf9\xc7\xca#E\ +vB\xa09x~\x8d\xd0\xa4\xaa\xbc\x80\xd2\x06\x9f\xec\ +(\xee,M\x85\x1ej\xa2\x99\xce\x16\xbe\x8c\x95\x064\ +/\xacgr_T\xd57}c\xf5M\xbf\xd1\x94\xf3\ +\xa3\xad\xbcv\xa9!\x0a\xa50f\xe1g\x1cf\x11[\ +c\x04\xb3\xea\x99da\x92@\xdcM\xd46\x9b\xb5\xf5\ +gEI\x0f\x03\xe7\x8eg\xcb\x8f\xe0+'\x81\x99\xa0\ +o\xc2\x14\x1a^c!\x11\x7f\xb6\xee\x8c\xee}\x89\xe5\ +\xb8\x95\xaa~E=\x00?1y}\xfa\x032<\x07\ +_\xf6^\x89-:\x82\x92\x11\x12-\x8b\x13j\xaa\x87\ +&4\x8b4TK\x83JG.\xda#lq\xf4\xd6\ +\xa0D.\xb2\xe2\xaao\xbd\xaa\x01\xd3\xac\xf4\xe0p\xd9\ +]\x0c\xf5\xe9t\x15\xf2JB\xcf5\x901\xbe&{\ +\x08\xbb\xf3\xa1\x9a\x12\xc5\x96n\x0d\xe0\x8a\xf2\xeec\xf2\ +\xa36M\xe8\x09\x89:Pv5\x12\x92\xf3\xaa[\xd5\ +7_\xa8W\x8b\x1d\x06uO\xa9\xb6\xaai\xd8\x01\xbe\ +zo\xd2\x86\x83\xb8,\xa0H\x12`\xb0|`\xd6\xae\ +\xffH\xa7\xfe#\xdd\xfa\x8f\xf4\x9a\x86},\xd4X\xd7\ +T\xda\xaa9\xd4\xdd8\xe1\xd2\xe1\x94\xfdd\xeb\xe9i\ +\xfa`\xa7\xe9\x83\xdd\xa6\x0f\xf6\xec\xb3M\xea\x12\xaf\xe2\ +\xae\xdb\x93\xaf\xf1\xa3\x9d\xe6\x8fv\x9b?\xda\xab\x93f\ +S\x97\x8cL\xcc7\xa1c\xf3g;;<\xdb\xdd\xe1\ +\xd9\x9eE\x16&o\x86U\xb30\x95Y\x06c$\x0b\ +\xb0\x9b\xe0\x06\x87\x97\x1aC]g\xb9\x17]d=<\ +\xc3>7\xf5g\xa6x\xcc\xccgA\x1f\xd8\x9f\x7f\xe8\ +\x13\x09\xaa\xae\xa8\xa2\xa3\xffa]\xc9\xfej\xba/\xc1\ +\xca\xba\xbf\xeeU(\x03\xe8K]k\xbc\xd3X\xf9\xa6\ +\xe2&/\x18\x96\xbdKX\x8f\xe4s\xbfR\xb8\xfa\xf5\ +n\xaf\x146`ox\xd7e\x11\xf5y\xfd+(z\ +\x02\xd9\x09\xe0\xdc\xfb\xe0\x15\x9c\xfe_\xb1\xc5\xa1\x19\xd3\ +w\x08ok\xc1\x7f\xeb(u\x15\xf8\xcf\x10XVc\ +\xaf.\xc0\xfc.\xc8[\xd0\xf7\xbbWcZ\xa1\xf2\xf7\ +\x14e\x9aQ\xfb\x0e\xb5\x99M\x90\xf8\xd1J4\x9b\x8f\ +\xe1\xdbUj\xee\x80\xd3\xb7,\xd8|\x06\xb4\xbeY\xdd\ +&y1\x0d9\x92\xf6\xc2\xc7\xad\xc0\x099=\x1a\xd0\ +\xadmN\x0fg_\xdc\xd1\xc3\xc7\xe8\xf7\xcfx\x0eV\ +\xf9\xccU\x1dX\x8b\xca\x14\x03\x02\xa7\xe4\xc7\xb2.\xa9\ +\xc7^\x7fb\xccdd\xf1l[\xfc\x8d/\x06\xa7!\ +4\xeb\xfe\x8c\xf5*\xda\xc5u\xdeC4\xb3\x01\xa7+\ +'\xfa\x0f\xdfM\xba\xd4\ +\x00\x00\x02\xfe\ +<\ +svg xmlns=\x22http:\ +//www.w3.org/200\ +0/svg\x22 height=\x224\ +0px\x22 viewBox=\x220 \ +-960 960 960\x22 wi\ +dth=\x2240px\x22 fill=\ +\x22#000000\x22>\ +\x00\x00\x021\ +<\ +svg xmlns=\x22http:\ +//www.w3.org/200\ +0/svg\x22 height=\x224\ +0px\x22 viewBox=\x220 \ +-960 960 960\x22 wi\ +dth=\x2240px\x22 fill=\ +\x22#FFFFFF\x22>\ +\ +\x00\x00\x02\xd3\ +<\ +svg xmlns=\x22http:\ +//www.w3.org/200\ +0/svg\x22 height=\x224\ +0px\x22 viewBox=\x220 \ +-960 960 960\x22 wi\ +dth=\x2240px\x22 fill=\ +\x22#FFFFFF\x22>\ +\x00\x00\x05L\ +<\ +svg width=\x2240\x22 h\ +eight=\x2240\x22 viewB\ +ox=\x220 0 40 40\x22 f\ +ill=\x22none\x22 xmlns\ +=\x22http://www.w3.\ +org/2000/svg\x22>\x0a<\ +path d=\x22M20.0001\ + 15.1667L13.7222\ + 21.4722H26.3055\ +L20.0001 15.1667\ +ZM20.0001 3.3334\ +1C22.287 3.33341\ + 24.4444 3.77091\ + 26.4722 4.64591\ +C28.4999 5.52091\ + 30.2686 6.713 3\ +1.778 8.22216C33\ +.2872 9.73161 34\ +.4792 11.5002 35\ +.3542 13.528C36.\ +2292 15.5558 36.\ +6667 17.7131 36.\ +6667 20.0001C36.\ +6667 22.3056 36.\ +2292 24.4723 35.\ +3542 26.5001C34.\ +4792 28.5279 33.\ +2872 30.2917 31.\ +778 31.7917C30.2\ +686 33.2917 28.4\ +999 34.4792 26.4\ +722 35.3542C24.4\ +444 36.2292 22.2\ +87 36.6667 20.00\ +01 36.6667C17.69\ +45 36.6667 15.52\ +79 36.2292 13.50\ +01 35.3542C11.47\ +23 34.4792 9.708\ +41 33.2917 8.208\ +41 31.7917C6.708\ +41 30.2917 5.520\ +91 28.5279 4.645\ +91 26.5001C3.770\ +91 24.4723 3.333\ +41 22.3056 3.333\ +41 20.0001C3.333\ +41 17.7131 3.770\ +91 15.5558 4.645\ +91 13.528C5.5209\ +1 11.5002 6.7084\ +1 9.73161 8.2084\ +1 8.22216C9.7084\ +1 6.713 11.4723 \ +5.52091 13.5001 \ +4.64591C15.5279 \ +3.77091 17.6945 \ +3.33341 20.0001 \ +3.33341ZM20.0001\ + 6.11133C16.1298\ + 6.11133 12.8474\ + 7.46313 10.153 \ +10.1667C7.45855 \ +12.8704 6.11133 \ +16.1481 6.11133 \ +20.0001C6.11133 \ +23.8704 7.45855 \ +27.1527 10.153 2\ +9.8472C12.8474 3\ +2.5416 16.1298 3\ +3.8888 20.0001 3\ +3.8888C23.852 33\ +.8888 27.1298 32\ +.5416 29.8334 29\ +.8472C32.537 27.\ +1527 33.8888 23.\ +8704 33.8888 20.\ +0001C33.8888 16.\ +1481 32.537 12.8\ +704 29.8334 10.1\ +667C27.1298 7.46\ +313 23.852 6.111\ +33 20.0001 6.111\ +33Z\x22 fill=\x22black\ +\x22/>\x0a\x0a\ +\x00\x00\x02\xde\ +<\ +svg xmlns=\x22http:\ +//www.w3.org/200\ +0/svg\x22 height=\x224\ +0px\x22 viewBox=\x220 \ +-960 960 960\x22 wi\ +dth=\x2240px\x22 fill=\ +\x22#000000\x22>\ +\x00\x00\x021\ +<\ +svg xmlns=\x22http:\ +//www.w3.org/200\ +0/svg\x22 height=\x224\ +0px\x22 viewBox=\x220 \ +-960 960 960\x22 wi\ +dth=\x2240px\x22 fill=\ +\x22#000000\x22>\ +\ +\x00\x00\x05L\ +<\ +svg width=\x2240\x22 h\ +eight=\x2240\x22 viewB\ +ox=\x220 0 40 40\x22 f\ +ill=\x22none\x22 xmlns\ +=\x22http://www.w3.\ +org/2000/svg\x22>\x0a<\ +path d=\x22M20.0001\ + 15.1667L13.7222\ + 21.4722H26.3055\ +L20.0001 15.1667\ +ZM20.0001 3.3334\ +1C22.287 3.33341\ + 24.4444 3.77091\ + 26.4722 4.64591\ +C28.4999 5.52091\ + 30.2686 6.713 3\ +1.778 8.22216C33\ +.2872 9.73161 34\ +.4792 11.5002 35\ +.3542 13.528C36.\ +2292 15.5558 36.\ +6667 17.7131 36.\ +6667 20.0001C36.\ +6667 22.3056 36.\ +2292 24.4723 35.\ +3542 26.5001C34.\ +4792 28.5279 33.\ +2872 30.2917 31.\ +778 31.7917C30.2\ +686 33.2917 28.4\ +999 34.4792 26.4\ +722 35.3542C24.4\ +444 36.2292 22.2\ +87 36.6667 20.00\ +01 36.6667C17.69\ +45 36.6667 15.52\ +79 36.2292 13.50\ +01 35.3542C11.47\ +23 34.4792 9.708\ +41 33.2917 8.208\ +41 31.7917C6.708\ +41 30.2917 5.520\ +91 28.5279 4.645\ +91 26.5001C3.770\ +91 24.4723 3.333\ +41 22.3056 3.333\ +41 20.0001C3.333\ +41 17.7131 3.770\ +91 15.5558 4.645\ +91 13.528C5.5209\ +1 11.5002 6.7084\ +1 9.73161 8.2084\ +1 8.22216C9.7084\ +1 6.713 11.4723 \ +5.52091 13.5001 \ +4.64591C15.5279 \ +3.77091 17.6945 \ +3.33341 20.0001 \ +3.33341ZM20.0001\ + 6.11133C16.1298\ + 6.11133 12.8474\ + 7.46313 10.153 \ +10.1667C7.45855 \ +12.8704 6.11133 \ +16.1481 6.11133 \ +20.0001C6.11133 \ +23.8704 7.45855 \ +27.1527 10.153 2\ +9.8472C12.8474 3\ +2.5416 16.1298 3\ +3.8888 20.0001 3\ +3.8888C23.852 33\ +.8888 27.1298 32\ +.5416 29.8334 29\ +.8472C32.537 27.\ +1527 33.8888 23.\ +8704 33.8888 20.\ +0001C33.8888 16.\ +1481 32.537 12.8\ +704 29.8334 10.1\ +667C27.1298 7.46\ +313 23.852 6.111\ +33 20.0001 6.111\ +33Z\x22 fill=\x22white\ +\x22/>\x0a\x0a\ +\x00\x00\x02\xbb\ +<\ +svg xmlns=\x22http:\ +//www.w3.org/200\ +0/svg\x22 height=\x224\ +0px\x22 viewBox=\x220 \ +-960 960 960\x22 wi\ +dth=\x2240px\x22 fill=\ +\x22#FFFFFF\x22>\ +\x00\x00\x025\ +<\ +svg xmlns=\x22http:\ +//www.w3.org/200\ +0/svg\x22 height=\x224\ +0px\x22 viewBox=\x220 \ +-960 960 960\x22 wi\ +dth=\x2240px\x22 fill=\ +\x22#FFFFFF\x22>\ +" + +qt_resource_name = b"\ +\x00\x07\ +\x0c\xba\xb6s\ +\x00v\ +\x00e\x00c\x00t\x00o\x00r\x00s\ +\x00\x06\ +\x07\xae\xc3\xc3\ +\x00t\ +\x00h\x00e\x00m\x00e\x00s\ +\x00\x0e\ +\x03\x9b1c\ +\x00l\ +\x00i\x00g\x00h\x00t\x00s\x00t\x00y\x00l\x00e\x00.\x00q\x00s\x00s\ +\x00\x0e\ +\x0d\xd6\xeag\ +\x00l\ +\x00o\x00c\x00k\x00_\x00c\x00l\x00o\x00s\x00e\x00.\x00s\x00v\x00g\ +\x00\x13\ +\x0cPg\xa7\ +\x00a\ +\x00r\x00r\x00o\x00w\x00_\x00d\x00o\x00w\x00n\x00_\x00d\x00a\x00r\x00k\x00.\x00s\ +\x00v\x00g\ +\x00\x0a\ +\x0f\xec\x03\xe7\ +\x00d\ +\x00e\x00s\x00i\x00g\x00n\x00.\x00s\x00v\x00g\ +\x00\x12\ +\x0aDDG\ +\x00a\ +\x00r\x00r\x00o\x00w\x00_\x00u\x00p\x00_\x00l\x00i\x00g\x00h\x00t\x00.\x00s\x00v\ +\x00g\ +\x00\x0d\ +\x005w\xc7\ +\x00l\ +\x00o\x00c\x00k\x00_\x00o\x00p\x00e\x00n\x00.\x00s\x00v\x00g\ +\x00\x14\ +\x01\xd3}\xa7\ +\x00a\ +\x00r\x00r\x00o\x00w\x00_\x00d\x00o\x00w\x00n\x00_\x00l\x00i\x00g\x00h\x00t\x00.\ +\x00s\x00v\x00g\ +\x00\x11\ +\x03\xf9t'\ +\x00a\ +\x00r\x00r\x00o\x00w\x00_\x00u\x00p\x00_\x00d\x00a\x00r\x00k\x00.\x00s\x00v\x00g\ +\ +\x00\x11\ +\x08\xb1e\xc7\ +\x00d\ +\x00e\x00s\x00i\x00g\x00n\x00_\x00r\x00e\x00p\x00o\x00r\x00t\x00.\x00s\x00v\x00g\ +\ +\x00\x08\ +\x08\xc8U\xe7\ +\x00s\ +\x00a\x00v\x00e\x00.\x00s\x00v\x00g\ +" + +qt_resource_struct = b"\ +\x00\x00\x00\x00\x00\x02\x00\x00\x00\x02\x00\x00\x00\x01\ +\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x14\x00\x02\x00\x00\x00\x01\x00\x00\x00\x0c\ +\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x02\x00\x00\x00\x09\x00\x00\x00\x03\ +\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\xda\x00\x00\x00\x00\x00\x01\x00\x00#\xaa\ +\x00\x00\x01\x9a\xbaP\xcf{\ +\x00\x00\x00\xfa\x00\x00\x00\x00\x00\x01\x00\x00&\x8c\ +\x00\x00\x01\x9a\xb4\xb2\x08\xc2\ +\x00\x00\x01(\x00\x00\x00\x00\x00\x01\x00\x00(\xc1\ +\x00\x00\x01\x9a\xb4\xbe\x94N\ +\x00\x00\x01P\x00\x00\x00\x00\x00\x01\x00\x00.\x11\ +\x00\x00\x01\x9a\x0f\xf20:\ +\x00\x00\x01x\x00\x00\x00\x00\x00\x01\x00\x000\xd0\ +\x00\x00\x01\x9a\x0f\xf20\xf8\ +\x00\x00\x00\xb0\x00\x00\x00\x00\x00\x01\x00\x00\x1eZ\ +\x00\x00\x01\x9a\xb4\xbe\xd7\xa7\ +\x00\x00\x00j\x00\x00\x00\x00\x00\x01\x00\x00\x19N\ +\x00\x00\x01\x9a\xb4\xb1\xf3c\ +\x00\x00\x00H\x00\x00\x00\x00\x00\x01\x00\x00\x16L\ +\x00\x00\x01\x9a\xbaP\xb8\xcc\ +\x00\x00\x00\x96\x00\x00\x00\x00\x00\x01\x00\x00\x1b\x83\ +\x00\x00\x01\x9a\x0f\xf20\x18\ +\x00\x00\x00&\x00\x01\x00\x00\x00\x01\x00\x00\x00\x00\ +\x00\x00\x01\x9a\xb4\xfd=\x83\ +" + +def qInitResources(): + QtCore.qRegisterResourceData(0x03, qt_resource_struct, qt_resource_name, qt_resource_data) + +def qCleanupResources(): + QtCore.qUnregisterResourceData(0x03, qt_resource_struct, qt_resource_name, qt_resource_data) + +qInitResources() diff --git a/src/osbridge/resources/themes/darkstyle.qss b/src/osbridge/resources/themes/darkstyle.qss new file mode 100644 index 00000000..2e384e1b --- /dev/null +++ b/src/osbridge/resources/themes/darkstyle.qss @@ -0,0 +1,1646 @@ +/* ============================================== + OSDAG GUI STYLESHEET - ORGANIZED BY SPECIFICITY + ============================================== */ + +/* ============================================== + 1. GLOBAL STYLES (Least Specific) + ============================================== */ +* { + font-family: "Ubuntu Sans"; +} + +QMainWindow { + background-color: #282828; + border: 1px solid #6B7D20; + margin: 0px; + padding: 0px; +} + +QTabWidget { + background-color: #333333; + border: 0px; +} + +QToolTip { + background-color: #2B2B2B; + color: #D0D0D0; + border: 1px solid #6B7D20; + padding: 2px 2px; + font-size: 12px; + border-radius: 0px; + qproperty-alignment: AlignVCenter; +} + +/* ============================================== + 2. TRANSPARENT BACKGROUNDS + ============================================== */ +QWidget#search_overlay_scroll_content { + background: transparent; +} +QWidget#svg_card_area, +QWidget#home_top_right_container, +QWidget#mid_top_svg_lay_wrapper, +QWidget#pr_menu_container, +QWidget#svg_widget_background, +QWidget#sec_menu_container, +QFrame#actionsFrame { + background: transparent; +} + +QScrollArea#svgCard_scroll_area{ + background: transparent; + border: none; +} +QScrollArea#search_overlay_scrollarea { + background: transparent; + border: none; + border-radius: 12px; + margin: 3px; +} + +/* ============================================== + 3. GENERAL BUTTON STYLES (Base Level) + ============================================== */ +QPushButton { + background-color: #282828; + color: #D0D0D0; + font-weight: bold; + border-radius: 5px; + border: 1px solid #D0D0D0; + padding: 5px 14px; + text-align: center; +} + +QPushButton:hover { + background-color: #6B7D20; + border: 1px solid #6B7D20; + color: white; +} + +QPushButton:pressed { + color: black; + background-color: #383838; + border: 1px solid #6B7D20; +} + +/* ============================================== + 4. SPECIFIC BUTTON STYLES (Override General) + ============================================== */ + +/* Window Control Buttons */ +QPushButton#window_control_button, +QPushButton#close_button { + background-color: #353535; + color: white; + border-radius: 0px; + border: none; + padding: 0px; +} + +QPushButton#window_control_button:hover { + background-color: #444444; +} + +QPushButton#window_control_button:pressed { + background-color: #454545; +} + +QPushButton#close_button:hover { + background-color: #E81123; +} + +QPushButton#close_button:pressed { + background-color: #F1707A; +} + +/* Theme Toggle Button */ +QPushButton#themeToggle { + background: transparent; + border: none; + border-radius: 20px; +} + +QPushButton#themeToggle:hover { + background-color: rgba(255, 255, 255, 100); +} + +/* Additional Input Button */ +QPushButton#additional_input_btn { + background-color: #282828; + color: #D0D0D0; + font-weight: bold; + border-radius: 5px; + border: 1px solid #D0D0D0; + padding: 5px 14px; + text-align: center; +} + +QPushButton#additional_input_btn:hover { + background-color: #6B7D20; + border: 1px solid #6B7D20; + color: white; +} + +QPushButton#additional_input_btn:pressed { + color: #D0D0D0; + background-color: #282828; + border: 1px solid #D0D0D0; +} + +/* Navbar Button */ +QPushButton#navbar_button { + background-color: #333333; + color: #FFFFFF; + padding: 4px 4px 8px 8px; + font-weight: normal; + border-radius: 0px; + border: none; + border-top: 1px solid #333333; + border-bottom: 1px solid #333333; + text-align: left; +} + +QPushButton#navbar_button:hover { + background-color: transparent; + color: #90AF13; + border-top: 1px solid #6B7D20; + border-bottom: 1px solid #6B7D20; +} + +QPushButton#navbar_button[state="active"] { + background-color: #6B7D20; + color: #ffffff; + border-radius: 0px; + border: none; + padding: 4px 4px 8px 8px; + border-top: 1px solid #6B7D20; + border-bottom: 1px solid #6B7D20; + text-align: left; +} + +/* Menu Button */ +QPushButton#menu_button { + font-size: 14px; + width: 140px; + color: #FFFFFF; + background-color: #282828; + border: 2px solid #E5E5E5; + border-radius: 5px; + padding: 8px 4px; + margin: 1px 2px; +} + +QPushButton#menu_button:hover { + border: 2px solid #6B7D20; +} + +QPushButton#menu_button:pressed { + background-color: #6B7D20; + border: 2px solid #6B7D20; + color: white; +} + +QPushButton#menu_button[selected="true"] { + color: white; + background-color: #6B7D20; + border: 2px solid #6B7D20; +} + +/* Dock Custom Button */ +QPushButton#dock_custom_button { + background-color: #6B7D20; + border: 0px; + border-radius: 5px; + padding: 10px; + text-align: center; +} + +QPushButton#dock_custom_button:pressed { + background-color: #7d9710; +} + +/* Search Result Project Button */ +QPushButton#search_result_proj_btn { + background-color: #FFFFFF; + border-radius: 12px; + color: #2d3748; + border: 1px solid #cccccc; + padding: 4px 5px; + font-size: 10px; + font-weight: 600; +} + +QPushButton#search_result_proj_btn:hover { + background-color: #f0f4e3; + border-color: #9BC53D; +} + +QPushButton#search_result_proj_btn:pressed { + background-color: #d6e6be; +} + +/* ============================================== + 5. NESTED WIDGET STYLES (Most Specific) + ============================================== */ + +/* Dock Custom Button Children */ +QPushButton#dock_custom_button QLabel#button_label { + background: transparent; + color: white; +} + +QPushButton#dock_custom_button QSvgWidget#button_icon { + background: transparent; +} + +/* ============================================== + 6. TAB BAR STYLES + ============================================== */ +QTabBar::tab { + background: #282828; + border-left: 1px solid rgb(121, 115, 115); + border-right: 1px solid rgb(121, 115, 115); + border-top: 1px solid #353535; + border-bottom: 1px solid #353535; + padding: 6px 18px 6px 18px; + color: #FFFFFF; + font-size: 11px; + margin-left: 0px; +} + +QTabBar::tab:selected { + background: #333333; + color: #FFFFFF; + border: 1px solid #90AF13; + border-bottom: 1px solid #333333; + padding: 6px 18px 6px 18px; +} + +QTabBar::tab:hover { + border-top: 1px solid #90AF13; + border-left: 1px solid #90AF13; + border-right: 1px solid #90AF13; +} + +QTabBar::close-button { + image: url(:/vectors/window_close_dark.svg); + subcontrol-origin: padding; + subcontrol-position: center right; +} + +QTabBar::close-button:hover { + image: url(:/vectors/window_close_hover.svg); +} + +/* ============================================== + 7. FRAME STYLES + ============================================== */ +QWidget#BottomLine { + background-color: #6B7D20; +} + +QFrame#navbar_header { + background-color: #333333; +} + +QFrame#overlayFrame { + background-color: #282828; + border: 1px solid #e2e8f0; + border-radius: 15px; +} + +QFrame#SvgCard { + border-radius: 12px; + background-color: #282828; + border: 2px solid #E5E5E5; +} + +QFrame#SvgCard[state="default"] { + border-radius: 12px; + background-color: #282828; + border: 2px solid #E5E5E5; +} + +QFrame#SvgCard[state="selected"] { + background-color: #6B7D20; + border: 2px solid #90AF13; + border-radius: 12px; +} + +QFrame#SvgCard[state="hover"] { + background-color: #6B7D20; + border: 2px solid #90AF13; + border-radius: 12px; +} + +QFrame#search_result_item { + background: #282828; + border: 1px solid #282828; + color: #FFFFFF; + border-radius: 15px; + padding: 4px 12px; +} + +QFrame#search_result_item:hover { + background: #6B7D20; +} + +/* ============================================== + 8. LABEL STYLES + ============================================== */ +QLabel#version_label { + color: gray; +} + +QLabel#module_section_label { + color: #FFFFFF; + font-size: 16px; + background-color: rgba(138, 138, 138, 110); + padding: 2px 0px; + border-top: 1px solid #6B7D20; + border-bottom: 1px solid #6B7D20; +} + +QLabel#svgCard_title { + font-weight: bold; + color: #FFFFFF; + background: transparent; + font-size: 13px; + margin-top: 5px; +} + +QLabel#under_dev_label { + color: #FFFFFF; + font-size: 16px; +} + +QLabel#svgCard_open_label { + background-color: #333333; + color: #FFFFFF; + font-weight: bold; + border-top: 2px solid #6B7D20; + border-bottom-left-radius: 12px; + border-bottom-right-radius: 12px; + padding: 0px; + margin: 0px; +} + +QLabel#svgCard_open_label[state="default"] { + background-color: #333333; + color: #FFFFFF; + font-weight: bold; + border-top: 2px solid #6B7D20; + border-bottom-left-radius: 12px; + border-bottom-right-radius: 12px; + padding: 0px; + margin: 0px; +} + +QLabel#svgCard_open_label:hover { + background-color: #282828; + color: #6B7D20; + font-weight: bold; + border-top: 2px solid #6B7D20; + border-bottom-left-radius: 12px; + border-bottom-right-radius: 12px; + padding: 0px; + margin: 0px; +} + +QLabel#svgCard_open_label[state="selected"] { + background-color: #333333; + color: black; + font-weight: bold; + border-top: 2px solid #6B7D20; + border-bottom-left-radius: 12px; + border-bottom-right-radius: 12px; + padding: 0px; + margin: 0px; +} + +/* Search Overlay Labels */ +QLabel#projectsHeader { + color: #FFFFFF; + font-size: 12px; + font-weight: bold; + padding: 8px 16px; + border-radius: 12px; + background-color: #282828; + border-bottom: 1px solid #E2E8F0; +} + +QLabel#modulesHeader { + color: #FFFFFF; + font-size: 12px; + font-weight: bold; + padding: 8px 16px; + border-radius: 12px; + background-color: #282828; + border-top: 1px solid #E2E8F0; + border-bottom: 1px solid #E2E8F0; +} + +QLabel#noResults { + color: #718096; + font-size: 13px; + padding: 24px; +} + +QLabel#primaryText { + color: #FFFFFF; + font-size: 13px; + font-weight: 600; + background: transparent; +} + +QLabel#secondaryText { + color: #718096; + font-size: 12px; + background: transparent; +} + +QLabel#dateText { + color: #a0aec0; + background: transparent; + font-size: 11px; +} + +QLabel#iconLabel { + color: #FFFFFF; + font-size: 16px; + background: transparent; + font-weight: bold; +} + +/* ============================================== + 9. SCROLLBAR STYLES + ============================================== */ +QScrollArea#search_overlay_scrollarea QScrollBar:vertical { + border: none; + background: #f7fafc; + width: 3px; + margin-top: 8px; + margin-bottom: 8px; +} + +QScrollArea#search_overlay_scrollarea QScrollBar::handle:vertical { + background: #cbd5e0; + border-radius: 2px; + min-height: 20px; +} + +QScrollArea#search_overlay_scrollarea QScrollBar::handle:vertical:hover { + background: #a0aec0; +} + +QScrollArea#search_overlay_scrollarea QScrollBar::add-line:vertical, +QScrollArea#search_overlay_scrollarea QScrollBar::sub-line:vertical { + border: none; + background: none; +} + +QScrollArea#search_overlay_scrollarea QScrollBar::add-page:vertical, +QScrollArea#search_overlay_scrollarea QScrollBar::sub-page:vertical { + background: none; +} + +QScrollArea#recents_scroll_area{ + background: transparent; + border: none; + padding: 10px; +} + +/* Scrollbar itself */ +QScrollArea#recents_scroll_area QScrollBar:vertical { + border: none; + background: #f0f0f0; + width: 8px; + margin: 0px 0px 0px 0px; +} + +/* Handle */ +QScrollArea#recents_scroll_area QScrollBar::handle:vertical { + background: #c0c0c0; + border-radius: 4px; + min-height: 20px; +} + +/* Handle on hover */ +QScrollArea#recents_scroll_area QScrollBar::handle:vertical:hover { + background: #a0a0a0; +} + +/* Handle when pressed */ +QScrollArea#recents_scroll_area QScrollBar::handle:vertical:pressed { + background: #808080; /* Even darker grey when pressed */ +} + +/* Remove add/sub line buttons (arrows) */ +QScrollArea#recents_scroll_area QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical { + border: none; + background: none; +} + +/* Styling the page area (the track around the handle) */ +QScrollArea#recents_scroll_area QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { + background: none; /* Make the page area transparent, letting the QScrollBar background show through */ +} + +/* Default button style */ +QPushButton#TopButton { + background: #282828; + border-radius: 0px; + border: 1px solid #E5E5E5; + color: #FFFFFF; + font-size: 1px; + padding: 10px; + text-align: center; +} + +/* Hover button style */ +QPushButton#TopButton[hover="true"] { + background: #6B7D20; + border: 1px solid #6B7D20; + color: #FFFFFF; + font-size: 14px; + font-weight: 600; + padding: 10px; + text-align: center; +} + +/* Pressed button style */ +QPushButton#TopButton[pressed="true"] { + background: #90AF13; + border: 1px solid #90AF13; + color: #FFFFFF; + font-size: 14px; + font-weight: 600; + padding: 10px; + text-align: center; +} + +/* Dropdown menu style */ +QMenu { + background: #282828; + border: 1px solid #6B7D20; + font-size: 14px; + padding: 0px; +} + +QMenu::item { + padding: 8px 16px; + color: #FFFFFF; + border: none; + margin: 1px; +} + +QMenu::item:selected { + background: #6B7D20; + color: #FFFFFF; + border-radius: 2px; +} + +QWidget#searchContainer { + border: 2px solid #E5E5E5; + border-radius: 24px; + background: #282828; + color: #FFFFFF; + min-height: 48px; + padding: 0px 8px; +} +QWidget#searchContainer[focused="true"] { + border-color: #6B7D20; +} +QLineEdit#searchInput { + border: none; + background: transparent; + font-size: 16px; + color: #FFFFFF; +} +QLineEdit#searchInput:focus { + outline: none; + border: none; +} +QLabel#shortcutKey, #lKey { + background: #333333; + border: 1px solid #333333; + border-radius: 4px; + color: #FFFFFF; + font-size: 11px; + font-weight: 600; + padding: 2px 4px; +} +QLabel#plusLabel { + color: #FFFFFF; + font-size: 12px; + font-weight: 500; +} + +QFrame#projectItem{ + background: #282828; + border: 1px solid #E2E8F0; + margin: 2px 4px 6px 4px; + border-radius: 10px; +} +QFrame#projectItem:hover{ + background: #6B7D20; + border-color: #6B7D20; +} +QFrame#projectItem[selected="true"] { + background: #6B7D20; + border-color: #6B7D20; +} +QLabel#projectName{ + color: #FFFFFF; + font-size: 13px; +} +QLabel#submoduleName, +QLabel#dateLabel { + color: #E2E8F0; + font-size: 11px; +} + +QLabel#record_icon_label { + font-weight: bold; + color: #FFFFFF; + font-size: 16px; +} + +QPushButton#recent_proj_btn { + background-color: #ffffff ; + border-radius: 12px; + color: #2d3748 ; + border: 1px solid #cccccc ; + padding: 4px 5px ; + font-size: 10px ; + font-weight: 600 ; +} +QPushButton#recent_proj_btn:hover { + background-color: #f0f4e3 ; + border-color: #9BC53D ; +} +QPushButton#recent_proj_btn:pressed { + background-color: #d6e6be ; +} + +QFrame#moduleItem { + background: #282828; + border: 1px solid #E2E8F0; + margin: 2px 4px 6px 4px; + border-radius: 10px; +} +QFrame#moduleItem:hover { + background: #6B7D20; + border-color: #6B7D20; +} +QLabel#moduleName { + color: #FFFFFF; + font-size: 13px; +} +QLabel#subModuleLabel, QLabel#dateLabel { + color: #E2E8F0; + font-size: 11px; +} + +QFrame#sectionFrame { + background: #282828; + border: 2px solid #e1e5e9; + border-radius: 18px; +} +QLabel#sectionHeading { + font-family: "Calibri", sans-serif; + font-size: 18px; + font-weight: bold; + color: #FFFFFF; + background: transparent; + padding: 8px; +} +QWidget#scrollContainer { + background: transparent; + border: none; + margin: 2px 2px 6px 2px; +} +QWidget#scrollContainer_empty { + background: #333333; + border: 1px solid #333333; + margin: 2px 2px 6px 2px; + border-radius: 10px; +} +QWidget#SplashScreen_CentralWidget { + border: 2px solid #90af13; + border-radius: 5px; + background-image: url(':/backgrounds/splash_bg.jpg'); + background-repeat: no-repeat; + background-position: center; +} +QLabel#splash_loading_label { + background-color: rgba(255,255,255,0.3); +} + +/*=============================================================== + cad view buttons +===============================================================*/ +QPushButton#cad_view_buttons { + background-color: #333333; + border: 0px; + border-radius: 0; + color: #D0D0D0; + padding: 0; + font-weight: bold; +} +QPushButton#cad_view_buttons:hover { + background-color: #6B7D20; + border: 1px solid #6B7D20; + color: white; +} +QPushButton#cad_view_buttons:pressed { + background-color: #333333; + border: 1px solid #6B7D20; + color: black; +} +/*===============================================================*/ + +QWidget#output_dock_indicator, +QWidget#input_dock_indicator { + background-color: #333333; +} + +/*=======================Template-Page===========================*/ + +QWidget#template_page { + background-color: #333333; + margin: 0px; + padding: 0px; +} +QWidget#control_btn_widget { + background-color: #383838; + padding: 0px; +} +QMenuBar#template_page_menu_bar { + background-color: #383838; + color:rgb(234, 234, 234); + padding: 0px; +} +QMenuBar#template_page_menu_bar::item { + padding: 5px 10px; + background: transparent; + border-radius: 0px; +} +QMenuBar#template_page_menu_bar::item:selected { + background: #282828; +} +QMenuBar#template_page_menu_bar::item:pressed { + background: rgb(94, 94, 94); +} +QMenuBar#template_page_menu_bar QMenu { + background-color: #383838; + border: 1px solid #D0D0D0; + border-radius: 4px; + padding: 0px; +} +QMenuBar#template_page_menu_bar QMenu::item { + padding: 5px; + color: #D0D0D0; + font-size: 11px; +} +QMenuBar#template_page_menu_bar QMenu::item:selected { + background-color:rgb(94, 94, 94); + border-radius: 3px; +} +QMenuBar#template_page_menu_bar QMenu::separator { + height: 1px; + background: #D0D0D0; + margin-left: 2px; + margin-right: 2px; + margin-top: 0px; + margin-bottom: 0px; +} +QMenuBar#template_page_menu_bar QMenu::right-arrow { + width: 8px; + height: 8px; +} +QWidget#toggle_strip { + background-color: #94b816; +} +QPushButton#toggle_strip_button { + background-color: #6B7D20; + color: white; + font-size: 12px; + font-weight: bold; + padding: 0px; + border: none; +} +QPushButton#toggle_strip_button:hover { + background-color: #5e7407; +} + +QWidget#cad_custom_selector { + padding: 5px; +} +QWidget#cad_custom_selector QCheckBox { + color: #FFFFFF; + margin: 5px; + border-style: solid; + border-width: 1px; + border-color: transparent; +} +QWidget#cad_custom_selector QCheckBox:disabled { + color: #808080; + margin: 5px; + border-style: solid; + border-width: 1px; + border-color: transparent; +} +QWidget#cad_custom_selector QCheckBox:hover { + border-radius: 4px; + border-style: solid; + border-width: 1px; + border-color: transparent; +} + +QWidget#cad_custom_selector QCheckBox::indicator { + width: 12px; + height: 12px; +} + +QWidget#cad_custom_selector QCheckBox::indicator:checked { + border-style: solid; + border-width: 1px; + border-color: qlineargradient( + spread:pad, x1:0.5, y1:1, x2:0.5, y2:0, + stop:0 rgba(120, 120, 120, 255), + stop:1 rgba(180, 180, 180, 255) + ); + background-color: qlineargradient( + spread:pad, x1:0.5, y1:1, x2:0.5, y2:0, + stop:0 rgba(120, 120, 120, 255), + stop:1 rgba(200, 200, 200, 255) + ); +} + +QWidget#cad_custom_selector QCheckBox::indicator:unchecked { + border-style: solid; + border-width: 1px; + border-color: qlineargradient( + spread:pad, x1:0.5, y1:1, x2:0.5, y2:0, + stop:0 rgba(160, 160, 160, 255), + stop:1 rgba(200, 200, 200, 255) + ); + background-color: #D0D0D0; +} +/*==========floating-navbar=================================*/ +QWidget#floating_btn_container { + background-color: #282828; + border: 1px solid #6B7D20; + border-top-left-radius: 5px; + border-top-right-radius: 5px; + border-bottom-left-radius: 5px; + border-bottom-right-radius: 5px; +} +QPushButton#floating_btn[state="default"] { + background-color: #282828; + border: 0px; + border-radius: 5px; + margin: 3px; + padding: 1px; +} +QPushButton#floating_btn[state="selected"] { + background-color: #6B7D20; + border-radius: 5px; + border: 0px; + margin: 3px; + padding: 1px; +} +QPushButton#floating_btn[state="active"] { + background-color: #6B7D20; + border-radius: 5px; + border: 0px; + margin: 3px; + padding: 1px; +} +/*=======================Input-Dock===========================*/ +QWidget#dock_splitter { + background-color: red; +} + +QWidget #inputs-leftpanel { + background-color: #333333; +} + +QWidget #outputs-rightpanel { + background-color: #333333; +} + +QScrollArea#inputs_vscrollarea, +QScrollArea#outputs_vscrollarea { + border: 1px solid #A0A0A0; + background-color: transparent; + padding: 3px; +} +QScrollArea#inputs_vscrollarea QScrollBar:vertical, +QScrollArea#outputs_vscrollarea QScrollBar:vertical { + background: #383838; + width: 8px; + margin: 0px 0px 0px 3px; + border-radius: 2px; +} +QScrollArea#inputs_vscrollarea QScrollBar::handle:vertical, +QScrollArea#outputs_vscrollarea QScrollBar::handle:vertical { + background: #A0A0A0; + min-height: 30px; + border-radius: 2px; +} +QScrollArea#inputs_vscrollarea QScrollBar::handle:vertical:hover, +QScrollArea#outputs_vscrollarea QScrollBar::handle:vertical:hover { + background: #707070; +} +QScrollArea#inputs_vscrollarea QScrollBar::add-line:vertical, +QScrollArea#inputs_vscrollarea QScrollBar::sub-line:vertical, +QScrollArea#outputs_vscrollarea QScrollBar::add-line:vertical, +QScrollArea#outputs_vscrollarea QScrollBar::sub-line:vertical { + height: 0px; +} +QScrollArea#inputs_vscrollarea QScrollBar::add-page:vertical, +QScrollArea#inputs_vscrollarea QScrollBar::sub-page:vertical, +QScrollArea#outputs_vscrollarea QScrollBar::add-page:vertical, +QScrollArea#outputs_vscrollarea QScrollBar::sub-page:vertical { + background: none; +} + +#inputs_container{ + background-color: #333333; + color: #D0D0D0; +} +#inputs_container QLabel{ + background: transparent; + color: #D0D0D0; +} +#inputs_container QComboBox{ + padding: 2px; + border: 1px solid #D0D0D0; + border-radius: 5px; + background-color: #333333; + color: #D0D0D0; +} +#inputs_container QComboBox::drop-down{ + subcontrol-origin: padding; + subcontrol-position: top right; + border-left: 0px; +} +#inputs_container QComboBox::down-arrow{ + image: url(:/images/down_arrow_dark.png); + width: 15px; + height: 15px; + margin-right: 5px; +} +#inputs_container QComboBox QAbstractItemView{ + background-color: #333333; + border: 1px solid #D0D0D0; + outline: none; +} +#inputs_container QComboBox QAbstractItemView::item{ + color: #D0D0D0; + background-color: #333333; + border: none; + border: 1px solid #333333; + border-radius: 0; + padding: 2px; +} +#inputs_container QComboBox QAbstractItemView::item:hover{ + border: 1px solid #6B7D20; + background-color: #6B7D20; + color: black; +} +#inputs_container QComboBox QAbstractItemView::item:selected{ + background-color: #6B7D20; + color: black; + border: 1px solid #6B7D20; +} +#inputs_container QComboBox QAbstractItemView::item:selected:hover{ + background-color: #6B7D20; + color: black; + border: 1px solid #6B7D20; +} +#inputs_container QLineEdit { + padding: 1px 7px; + border: 1px solid #D0D0D0; + border-radius: 6px; + background-color: #333333; + color: #D0D0D0; + font-weight: normal; +} +#inputs_container QGroupBox{ + border: 1px solid #6B7D20; + color: #D0D0D0; + border-radius: 4px; + margin-top: 0.8em; + font-weight: bold; +} +#inputs_container QGroupBox::title{ + subcontrol-origin: content; + subcontrol-position: top left; + left: 10px; + padding: 0 4px; + margin-top: -12px; + background-color: #333333; +} + +/*=======================Output-Dock===========================*/ + +QWidget#outputs_group_container, +QWidget#outputs_container{ + background-color: #333333; +} +#outputs_container QLabel{ + background: transparent; + color: #D0D0D0; +} +#outputs_container QLineEdit { + padding: 2px 7px; + border: 1px solid #D0D0D0; + border-radius: 4px; + background-color: #333333; + color: #D0D0D0; + font-weight: normal; +} +#outputs_container QGroupBox { + border: 1px solid #6B7D20; + color: #D0D0D0; + border-radius: 4px; + margin-top: 0.8em; + font-weight: bold; +} +#outputs_container QGroupBox::title { + subcontrol-origin: content; + subcontrol-position: top left; + left: 10px; + padding: 0 4px; + margin-top: -12px; + background-color: #333333; +} +#outputs_container QPushButton { + padding: 3px 9px; + background-color: #888888; + color: white; + border: 0px; + border-radius: 4px; + min-width: 100px; + max-width: 120px; + font-size: 12px; +} +#outputs_container QPushButton:disabled { + background-color: #cccccc; + color: #888888; +} + +QPushButton#inputs_button, +QPushButton#outputs_button { + background-color: #6B7D20; + border: 0px; + color: white; + font-weight: bold; + border-radius: 4px; + padding: 7px 14px; +} +QDialog#custom_material_popup { + background-color: #333333; + color: #FFFFFF; + border: 1px solid #90AF13; +} +#custom_material_popup QLabel { + color: #ffffff; +} +#custom_material_popup QLineEdit { + background-color: #333333; + color: #FFFFFF; + border: 1px solid #D0D0D0; + border-radius: 3px; + padding: 2px 4px; +} +#custom_material_popup QLineEdit[readOnly="true"] { + background-color: #282828; +} +#custom_material_popup QLineEdit:focus { + border: 1px solid #90af13; +} +#custom_material_popup QPushButton { + background-color: #90af13; + color: #ffffff; + padding: 6px 16px; + border-radius: 3px; + border: none; + font-weight: bold; +} +#custom_material_popup QPushButton:hover { + background-color: #7a9a12; +} +#custom_material_popup QPushButton:pressed { + background-color: #5f7a0e; +} + +QScrollArea#inputs_hscrollarea, +QScrollArea#outputs_hscrollarea { + border: none; + background: transparent; +} +QScrollArea#inputs_hscrollarea QScrollBar:horizontal, +QScrollArea#outputs_hscrollarea QScrollBar:horizontal { + background: #E0E0E0; + height: 8px; + margin: 3px 0px 0px 0px; + border-radius: 2px; +} +QScrollArea#inputs_hscrollarea QScrollBar::handle:horizontal, +QScrollArea#outputs_hscrollarea QScrollBar::handle:horizontal { + background: #A0A0A0; + min-width: 30px; + border-radius: 2px; +} +QScrollArea#inputs_hscrollarea QScrollBar::handle:horizontal:hover, +QScrollArea#outputs_hscrollarea QScrollBar::handle:horizontal:hover { + background: #707070; +} +QScrollArea#inputs_hscrollarea QScrollBar::add-line:horizontal, QScrollBar::sub-line:horizontal, +QScrollArea#outputs_hscrollarea QScrollBar::add-line:horizontal, QScrollBar::sub-line:horizontal { + width: 0px; +} +QScrollArea#inputs_hscrollarea QScrollBar::add-page:horizontal, QScrollBar::sub-page:horizontal, +QScrollArea#outputs_hscrollarea QScrollBar::add-page:horizontal, QScrollBar::sub-page:horizontal { + background: none; +} + +/*=======================Logs-Dock===========================*/ +QWidget#logs_dock QLabel { + background-color: #333333; + color: #D0D0D0; + padding: 3px; + font-weight: bold; + font-size: 12px; +} +QWidget#logs_dock QTextEdit { + background-color: #333333; + border: 1px solid #D0D0D0; + font-family: monospace; + font-size: 12px; + padding: 5px; + color: #D0D0D0; +} +QWidget#logs_dock QScrollBar:vertical { + background: #E0E0E0; /* Light grey for the scrollbar track */ + width: 8px; + margin: 0px 0px 0px 3px; + border-radius: 2px; +} +QWidget#logs_dock QScrollBar::handle:vertical { + background: #A0A0A0; /* Medium grey for the scrollbar handle */ + min-height: 30px; + border-radius: 2px; +} +QWidget#logs_dock QScrollBar::handle:vertical:hover { + background: #707070; /* Darker grey on hover for the handle */ +} +QWidget#logs_dock QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical { + height: 0px; /* Hides the up/down arrows */ +} +QWidget#logs_dock QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { + background: none; /* Hides the area between handle and arrows */ +} + +/*=======================dialogs===========================*/ +QDialog#spacing_capacity_details QScrollArea, +QDialog#spacing_capacity_details #spacing_scroll_widget, +QDialog#spacing_dialog QScrollArea, +QDialog#spacing_dialog QWidget#scroll_widget{ + background-color: #333333; +} +QDialog#TutorialsDialog, +QDialog#AskQuestions, +QDialog#AboutOsdagDialog, +QDialog#UpdateDialog, +QDialog#CustomDialog, +QDialog#design_report_popup, +QDialog#spacing_capacity_details, +QDialog#customized_input, +QDialog#spacing_dialog { + background-color: #333333; + border: 1px solid #6B7D20; +} +QWidget#CustomTitleBar { + background-color: #282828; +} +QWidget#BottomLine { + background-color: #6B7D20; +} +QLabel#message_lbl1 { + font-size: 14px; + font-weight: bold; + color: #D0D0D0; +} +QLabel#message_lbl2 { + font-size: 12px; + color: #D0D0D0; +} +QLabel#TitleLabel { + color: #D0D0D0; + padding: 0px; + background: transparent; +} + +QLabel#LogoLabel { + background: transparent; + color: #D0D0D0; + font-size: 14px; +} + +QToolButton#MinimizeButton { + background-color: transparent; + color: #FFFFFF; + border: 0px; + border-radius: 0px; + font-size: 16px; + border-radius: 0px; +} + +QToolButton#MinimizeButton:hover { + background-color: #444444; +} + +QToolButton#MinimizeButton:pressed { + background-color: #454545; +} + +QToolButton#MaxRestoreButton { + background-color: transparent; + color: #FFFFFF; + border: 0px; + border-radius: 0px; + font-size: 16px; + border-radius: 0px; +} + +QToolButton#MaxRestoreButton:hover { + background-color: #444444; +} + +QToolButton#MaxRestoreButton:pressed { + background-color: #454545; +} + +QToolButton#CloseButton { + background-color: transparent; + color: #FFFFFF; + border: 0px; + border-radius: 0px; + font-size: 16px; + border-radius: 0px; +} + +QToolButton#CloseButton:hover { + background-color: #e74c3c; + color: #ffffff; +} + +QToolButton#CloseButton:pressed { + background-color: #c0392b; +} + +QDialog#AboutOsdagDialog QWidget#ContentWidget, +QDialog#UpdateDialog QWidget#ContentWidget, +QDialog#customized_input QWidget#ContentWidget { + background-color: #333333; +} +QDialog#AboutOsdagDialog QTextBrowser, +QDialog#UpdateDialog QTextBrowser { + background-color: #333333; + border: 1px solid #D0D0D0; + border-radius: 4px; + font-family: 'Arial', sans-serif; + font-size: 8pt; + padding: 8px; +} +QDialog#AboutOsdagDialog QPushButton, +QDialog#UpdateDialog QPushButton { + background-color: #6B7D20; + color: white; + border: none; + border-radius: 5px; + padding: 5px 20px; + font-size: 12px; + font-weight: bold; +} +QDialog#AboutOsdagDialog QPushButton:hover, +QDialog#UpdateDialog QPushButton:hover { + background-color: #7A9611; +} +QDialog#AboutOsdagDialog QPushButton:pressed, +QDialog#UpdateDialog QPushButton:pressed { + background-color: #6B850F; +} +QDialog#design_report_popup QPushButton, +QDialog#spacing_capacity_details QPushButton{ + background-color: #333333; + color: #D0D0D0; + font-weight: bold; + border-radius: 5px; + border: 1px solid #D0D0D0; + padding: 5px 14px; + text-align: center; +} +QDialog#design_report_popup QPushButton:hover, +QDialog#spacing_capacity_details QPushButton:hover{ + background-color: #6B7D20; + border: 1px solid #6B7D20; + color: #D0D0D0; +} +QDialog#design_report_popup QPushButton:pressed, +QDialog#spacing_capacity_details QPushButton:pressed{ + color: #D0D0D0; + background-color: #333333; + border: 1px solid #D0D0D0; +} +QDialog#design_report_popup QLineEdit, +QDialog#design_report_popup QTextEdit, +QDialog#spacing_dialog QLineEdit { + padding: 2px 7px; + border: 1px solid #D0D0D0; + border-radius: 4px; + background-color: #333333; + color: #D0D0D0; + font-weight: normal; +} +QDialog#design_report_popup QLabel, +QDialog#spacing_capacity_details QLabel, +QDialog#spacing_dialog QLabel { + background: transparent; + color: #D0D0D0; +} + +QWidget#report_customization_widget QTreeWidget { + border: 1px solid #90AF13; + border-radius: 6px; + background-color: #333333; + show-decoration-selected: 1; +} +QWidget#report_customization_widget QTreeWidget::item { + padding: 4px 6px; + background-color: #333333; + color: #FFFFFF; + border-bottom: 1px solid #D0D0D0; +} +QWidget#report_customization_widget QTreeWidget::item:selected { + background-color: #363d18; + color: #FFFFFF; +} +QWidget#report_customization_widget QTreeWidget::item:hover { + background-color: #363d18; +} +QWidget#report_customization_widget QTreeWidget::indicator { + width: 14px; + height: 14px; +} +QWidget#report_customization_widget QTreeWidget::indicator:unchecked { + border: 1px solid #D0D0D0; + background: #333333; +} +QWidget#report_customization_widget QTreeWidget::indicator:checked { + border: 1px solid #555555; + background: #90AF13; + image: none; +} +QWidget#report_customization_widget QHeaderView::section { + background-color: #282828; + color: #FFFFFF; + padding-left: 5px; + border-radius: 0px; + border-top-left-radius: 6px; + border-top-right-radius: 6px; +} +QWidget#report_customization_widget QTreeWidget QHeaderView { + border-top-left-radius: 6px; + border-top-right-radius: 6px; +} +/*=============customized-inputs======================*/ +QDialog#customized_input QLabel#label, +QDialog#customized_input QLabel#label_2 { + font-weight: bold; + font-size: 13px; + color: #FFFFFF; + padding: 2px; +} +QDialog#customized_input QLabel#note_label { + background-color: #282828; + border: 1px solid #e0e0e0; + border-radius: 5px; + padding: 10px; + color: #FFFFFF; + font-size: 12px; +} +QDialog#customized_input QListWidget { + background-color: #333333; + border: 1px solid #E0E0E0; + border-radius: 5px; + padding: 5px; +} +QDialog#customized_input QListWidget::item { + border-left: 2px solid transparent; + background-color: transparent; + color: #FFFFFF; + padding: 3px; + margin: 1px; +} +QDialog#customized_input QListWidget::item:hover { + background-color: #363d18; + border-left-color: #90af13; +} +QDialog#customized_input QListWidget::item:selected { + background-color: #434b20; + color: black; + border-left: 2px solid #90AF13; +} +QDialog#customized_input QListWidget::item:selected:hover { + background-color: #363d18; + border-left-color: #7a9a12; +} +QDialog#customized_input QListWidget::item:disabled { + color: #FFFFFF; + background-color: #999999; +} +QDialog#customized_input QScrollBar:vertical { + background: #f0f0f0; + width: 5px; + margin: 0; + border-radius: 5px; +} +QDialog#customized_input QScrollBar::handle:vertical { + background: #c0c0c0; + min-height: 30px; + border-radius: 5px; +} +QDialog#customized_input QScrollBar::handle:vertical:hover { + background: #90AF13; +} +QDialog#customized_input QPushButton#pushButton, +QDialog#customized_input QPushButton#pushButton_2, +QDialog#customized_input QPushButton#pushButton_3, +QDialog#customized_input QPushButton#pushButton_4, +QDialog#customized_input QPushButton#pushButton_5 { + background-color: #90AF13; + color: white; + font-weight: bold; + font-size: 14px; + border-radius: 5px; + border: none; + padding: 5px; +} +QDialog#customized_input QPushButton#pushButton:hover, +QDialog#customized_input QPushButton#pushButton_2:hover, +QDialog#customized_input QPushButton#pushButton_3:hover, +QDialog#customized_input QPushButton#pushButton_4:hover, +QDialog#customized_input QPushButton#pushButton_5:hover { + background-color: #7a9a12; +} +QDialog#customized_input QPushButton#pushButton:pressed, +QDialog#customized_input QPushButton#pushButton_2:pressed, +QDialog#customized_input QPushButton#pushButton_3:pressed, +QDialog#customized_input QPushButton#pushButton_4:pressed, +QDialog#customized_input QPushButton#pushButton_5:pressed { + background-color: #5f7a0e; +} +QDialog#customized_input QPushButton#pushButton:disabled, +QDialog#customized_input QPushButton#pushButton_2:disabled, +QDialog#customized_input QPushButton#pushButton_3:disabled, +QDialog#customized_input QPushButton#pushButton_4:disabled, +QDialog#customized_input QPushButton#pushButton_5:disabled { + background-color: #d0d0d0; + color: #888888; +} +/*=======================Additional-Inputs=======================================*/ +QDialog#AdditionalInputs{ + background-color: #333333; + border: 1px solid #6B7D20; +} +QWidget#TableWidget QTabWidget QWidget{ + background-color: #333333; +} +QWidget#TableWidget QTabWidget > QWidget > QWidget > QWidget{ + background-color: #333333; + border: 1px solid #D0D0D0; +} +#AdditionalInputs QTabBar::tab { + background: #282828; + border: 1px solid #D0D0D0; + padding: 6px 18px 6px 18px; + color: #D0D0D0; + margin-left: 0px; +} +#AdditionalInputs QTabBar::tab:selected { + background: #333333; + color: #FFFFFF; + border: 1px solid #D0D0D0; + border-bottom: 1px solid #333333; + padding: 6px 18px 6px 18px; +} +#AdditionalInputs QTabBar::tab:hover { + border-bottom: 1px solid #333333; +} +/* Additional Input Button */ +QDialog#AdditionalInputs QPushButton { + background-color: #282828; + color: #D0D0D0; + font-weight: bold; + border-radius: 5px; + border: 1px solid #D0D0D0; + padding: 5px 14px; + text-align: center; +} +QDialog#AdditionalInputs QPushButton:hover { + background-color: #6B7D20; + border: 1px solid #6B7D20; + color: white; +} +QDialog#AdditionalInputs QPushButton:pressed { + color: #D0D0D0; + background-color: #282828; + border: 1px solid #D0D0D0; +} +QDialog#AdditionalInputs QLineEdit { + padding: 2px 7px; + border: 1px solid #D0D0D0; + border-radius: 4px; + background-color: #333333; + color: #D0D0D0; + font-weight: normal; +} +QDialog#AdditionalInputs QLabel { + color: #D0D0D0; +} +QDialog#AdditionalInputs QComboBox{ + padding: 2px 7px; + border: 1px solid #D0D0D0; + border-radius: 4px; + background-color: white; + color: #D0D0D0; +} +QDialog#AdditionalInputs QComboBox::drop-down{ + subcontrol-origin: padding; + subcontrol-position: top right; + border-left: 0px; +} +QDialog#AdditionalInputs QComboBox::down-arrow{ + image: url(:/images/down_arrow_light.png); + width: 15px; + height: 15px; + margin-right: 5px; +} +QDialog#AdditionalInputs QComboBox QAbstractItemView{ + background-color: #282828; + border: 1px solid #D0D0D0; + outline: none; +} +QDialog#AdditionalInputs QComboBox QAbstractItemView::item{ + color: #D0D0D0; + background-color: #282828; + border: none; + border: 1px solid #282828; + border-radius: 0; + padding: 2px; +} +QDialog#AdditionalInputs QComboBox QAbstractItemView::item:hover{ + border: 1px solid #6B7D20; + background-color: #6B7D20; + color: #D0D0D0; +} +QDialog#AdditionalInputs QComboBox QAbstractItemView::item:selected{ + background-color: #6B7D20; + color: #D0D0D0; + border: 1px solid #6B7D20; +} +QDialog#AdditionalInputs QComboBox QAbstractItemView::item:selected:hover{ + background-color: #6B7D20; + color: #D0D0D0; + border: 1px solid #6B7D20; +} +/* Plate-Girder Bounds Dialog */ +QDialog#BoundsSelectorDialog { + background-color: #333333; + border: 1px solid #6B7D20; + border-radius: 0px; +} +QDialog#BoundsSelectorDialog QLineEdit { + background-color: #333333; + border: 1px solid #D0D0D0; + border-radius: 4px; + padding: 5px 8px; + font-size: 12px; + color: #D0D0D0; +} +QDialog#BoundsSelectorDialog QLineEdit:hover { + border: 1px solid #999999; +} +QDialog#BoundsSelectorDialog QLineEdit:focus { + border: 1px solid #90AF13; + background-color: rgb(51, 51, 51); +} +QDialog#BoundsSelectorDialog QLabel { + color: #FFFFFF; +} \ No newline at end of file diff --git a/src/osbridge/resources/themes/lightstyle.qss b/src/osbridge/resources/themes/lightstyle.qss new file mode 100644 index 00000000..bfe319ef --- /dev/null +++ b/src/osbridge/resources/themes/lightstyle.qss @@ -0,0 +1,1607 @@ +/* ============================================== + OSDAG GUI STYLESHEET - ORGANIZED BY SPECIFICITY + ============================================== */ + +/* ============================================== + 1. GLOBAL STYLES (Least Specific) + ============================================== */ +* { + font-family: "Ubuntu Sans"; +} +QMainWindow { + background-color: #f4f4f4; + border: 1px solid #90af13; + margin: 0px; + padding: 0px; +} + +QToolTip { + background-color: #FFFFFF; + color: #000000; + border: 1px solid #90AF13; + padding: 2px 2px; + font-size: 12px; + border-radius: 0px; + qproperty-alignment: AlignVCenter; +} +QSplitter::handle { + background-color: #D0D0D0; +} + +/* ============================================== + 2. TRANSPARENT BACKGROUNDS + ============================================== */ +QWidget#search_overlay_scroll_content, +QWidget#svg_card_area, +QWidget#input_dock, +QWidget#output_dock +QWidget#home_top_right_container, +QWidget#mid_top_svg_lay_wrapper, +QWidget#pr_menu_container, +QWidget#svg_widget_background, +QWidget#sec_menu_container, +QFrame#actionsFrame { + background: transparent; +} + +QScrollArea#svgCard_scroll_area{ + background: transparent; + border: none; +} +QScrollArea#search_overlay_scrollarea { + background: transparent; + border: none; + border-radius: 12px; + margin: 3px; +} +/* ============================================== + 3. GENERAL BUTTON STYLES (Base Level) + ============================================== */ +QPushButton { + background-color: white; + color: black; + font-weight: bold; + border-radius: 5px; + border: 1px solid black; + padding: 5px 14px; + text-align: center; +} + +QPushButton:hover { + background-color: #90AF13; + border: 1px solid #90AF13; + color: white; +} + +QPushButton:pressed { + color: black; + background-color: white; + border: 1px solid black; +} + +/* ============================================== + 4. SPECIFIC BUTTON STYLES (Override General) + ============================================== */ + +/* Window Control Buttons */ +QPushButton#window_control_button, +QPushButton#close_button { + background-color: #f4f4f4; + color: white; + border-radius: 0px; + border: none; + padding: 0px; +} + +QPushButton#window_control_button:hover { + background-color: #d9d7d7; +} + +QPushButton#window_control_button:pressed { + background-color: #cfcfcf; +} + +QPushButton#close_button:hover { + background-color: #E81123; +} + +QPushButton#close_button:pressed { + background-color: #F1707A; +} + +/* Theme Toggle Button */ +QPushButton#themeToggle { + background: transparent; + border: none; + border-radius: 20px; +} + +QPushButton#themeToggle:hover { + background-color: rgba(255, 255, 255, 100); +} + +/* Navbar Button */ +QPushButton#navbar_button { + background-color: #ffffff; + color: #000000; + padding: 4px 4px 8px 8px; + font-weight: normal; + border-radius: 0px; + border: none; + border-top: 1px solid #ffffff; + border-bottom: 1px solid #ffffff; + text-align: left; +} + +QPushButton#navbar_button:hover { + background-color: transparent; + color: #90AF13; + border-top: 1px solid #90AF13; + border-bottom: 1px solid #90AF13; +} + +QPushButton#navbar_button[state="active"] { + background-color: #90AF13; + color: #ffffff; + border-radius: 0px; + border: none; + padding: 4px 4px 8px 8px; + border-top: 1px solid #90AF13; + border-bottom: 1px solid #90AF13; + text-align: left; +} + +/* Menu Button */ +QPushButton#menu_button { + font-size: 14px; + width: 140px; + color: black; + background-color: white; + border: 2px solid #E5E5E5; + border-radius: 5px; + padding: 8px 4px; + margin: 1px 2px; +} + +QPushButton#menu_button:hover { + border: 2px solid #90AF13; +} + +QPushButton#menu_button:pressed { + background-color: #90AF13; + border: 2px solid #90AF13; + color: white; +} + +QPushButton#menu_button[selected="true"] { + color: white; + background-color: #90AF13; + border: 2px solid #90AF13; +} + +/* Dock Custom Button */ +QPushButton#dock_custom_button { + background-color: #90AF13; + border: 0px; + border-radius: 5px; + padding: 10px; + text-align: center; +} + +QPushButton#dock_custom_button:pressed { + background-color: #7d9710; +} + +/* Search Result Project Button */ +QPushButton#search_result_proj_btn { + background-color: #ffffff; + border-radius: 12px; + color: #2d3748; + border: 1px solid #cccccc; + padding: 4px 5px; + font-size: 10px; + font-weight: 600; +} + +QPushButton#search_result_proj_btn:hover { + background-color: #f0f4e3; + border-color: #9BC53D; +} + +QPushButton#search_result_proj_btn:pressed { + background-color: #d6e6be; +} + +/* ============================================== + 5. NESTED WIDGET STYLES (Most Specific) + ============================================== */ + +/* Dock Custom Button Children */ +QPushButton#dock_custom_button QLabel#button_label { + background: transparent; + color: white; +} + +QPushButton#dock_custom_button QSvgWidget#button_icon { + background: transparent; +} + +/* ============================================== + 6. TAB BAR STYLES + ============================================== */ +QTabBar#main_tabs::tab { + background: #F4F4F4; + border-left: 1px solid #d9d7d7; + border-right: 1px solid #d9d7d7; + border-top: 1px solid #F4F4F4; + border-bottom: 1px solid #F4F4F4; + padding: 6px 18px 6px 18px; + color: #000000; + font-size: 11px; + margin-left: 0px; +} + +QTabBar#main_tabs::tab:selected { + background: #ffffff; + color: #000000; + border: 1px solid #90AF13; + border-bottom: 1px solid #ffffff; + padding: 6px 18px 6px 18px; +} + +QTabBar#main_tabs::tab:hover { + border-top: 1px solid #90AF13; + border-left: 1px solid #90AF13; + border-right: 1px solid #90AF13; +} + +QTabBar#main_tabs::close-button { + image: url(:/vectors/window_close_light.svg); + subcontrol-origin: padding; + subcontrol-position: center right; +} + +QTabBar#main_tabs::close-button:hover { + image: url(:/vectors/window_close_hover.svg); +} +/* ============================================== + 7. FRAME STYLES + ============================================== */ +QWidget#BottomLine { + background-color: #90af13; +} + +QFrame#navbar_header { + background-color: #ffffff; +} + +QFrame#overlayFrame { + background-color: white; + border: 2px solid #e2e8f0; + border-radius: 15px; +} + +QFrame#SvgCard { + border-radius: 12px; + background-color: rgb(255, 255, 255); + border: 2px solid #E5E5E5; +} + +QFrame#SvgCard[state="default"] { + border-radius: 12px; + background-color: rgb(255, 255, 255); + border: 2px solid #E5E5E5; +} + +QFrame#SvgCard[state="selected"] { + background-color: rgb(144, 175, 19); + border: 2px solid #90AF13; + border-radius: 12px; +} + +QFrame#SvgCard[state="hover"] { + background-color: rgb(144, 175, 19); + border: 2px solid #90AF13; + border-radius: 12px; +} + +QFrame#search_result_item { + background: white; + border: 1px solid white; + border-radius: 15px; + padding: 4px 12px; +} + +QFrame#search_result_item:hover { + border: 1px solid #9BC53D; + background: #D5E49B; +} + +/* ============================================== + 8. LABEL STYLES + ============================================== */ +QLabel#version_label { + color: gray; +} + +QLabel#module_section_label { + color: #000000; + font-size: 16px; + background-color: rgba(255, 255, 255, 150); + padding: 2px 0px; + border-top: 1px solid #90AF13; + border-bottom: 1px solid #90AF13; +} + +QLabel#svgCard_title { + font-weight: bold; + background: transparent; + font-size: 13px; + margin-top: 5px; +} + +QLabel#under_dev_label { + color: #000000; + font-size: 16px; +} + +QLabel#svgCard_open_label { + background-color: white; + color: black; + font-weight: bold; + border-top: 2px solid #7ba525; + border-bottom-left-radius: 12px; + border-bottom-right-radius: 12px; + padding: 0px; + margin: 0px; +} + +QLabel#svgCard_open_label[state="default"] { + background-color: white; + color: black; + font-weight: bold; + border-top: 2px solid #7ba525; + border-bottom-left-radius: 12px; + border-bottom-right-radius: 12px; + padding: 0px; + margin: 0px; +} + +QLabel#svgCard_open_label:hover { + background-color: white; + color: #90AF13; + font-weight: bold; + border-top: 2px solid #7ba525; + border-bottom-left-radius: 12px; + border-bottom-right-radius: 12px; + padding: 0px; + margin: 0px; +} + +QLabel#svgCard_open_label[state="selected"] { + background-color: white; + color: black; + font-weight: bold; + border-top: 2px solid #7ba525; + border-bottom-left-radius: 12px; + border-bottom-right-radius: 12px; + padding: 0px; + margin: 0px; +} + +/* Search Overlay Labels */ +QLabel#projectsHeader { + color: #2d3748; + font-size: 12px; + font-weight: bold; + padding: 8px 16px; + border-radius: 12px; + background-color: #f7fafc; + border-bottom: 1px solid #e2e8f0; +} + +QLabel#modulesHeader { + color: #2d3748; + font-size: 12px; + font-weight: bold; + padding: 8px 16px; + border-radius: 12px; + background-color: #f7fafc; + border-top: 1px solid #e2e8f0; + border-bottom: 1px solid #e2e8f0; +} + +QLabel#noResults { + color: #718096; + font-size: 13px; + padding: 24px; +} + +QLabel#primaryText { + color: #1a202c; + font-size: 13px; + font-weight: 600; + background: transparent; +} + +QLabel#secondaryText { + color: #718096; + font-size: 12px; + background: transparent; +} + +QLabel#dateText { + color: #a0aec0; + background: transparent; + font-size: 11px; +} + +QLabel#iconLabel { + color: #2d3748; + font-size: 16px; + background: transparent; + font-weight: bold; +} + +/* ============================================== + 9. SCROLLBAR STYLES + ============================================== */ +QScrollArea#search_overlay_scrollarea QScrollBar:vertical { + border: none; + background: #f7fafc; + width: 3px; + margin-top: 8px; + margin-bottom: 8px; +} + +QScrollArea#search_overlay_scrollarea QScrollBar::handle:vertical { + background: #cbd5e0; + border-radius: 2px; + min-height: 20px; +} + +QScrollArea#search_overlay_scrollarea QScrollBar::handle:vertical:hover { + background: #a0aec0; +} + +QScrollArea#search_overlay_scrollarea QScrollBar::add-line:vertical, +QScrollArea#search_overlay_scrollarea QScrollBar::sub-line:vertical { + border: none; + background: none; +} + +QScrollArea#search_overlay_scrollarea QScrollBar::add-page:vertical, +QScrollArea#search_overlay_scrollarea QScrollBar::sub-page:vertical { + background: none; +} + +QScrollArea#recents_scroll_area{ + background: transparent; + border: none; + padding: 10px; +} + +/* Scrollbar itself */ +QScrollArea#recents_scroll_area QScrollBar:vertical { + border: none; + background: #f0f0f0; + width: 8px; + margin: 0px 0px 0px 0px; +} + +/* Handle */ +QScrollArea#recents_scroll_area QScrollBar::handle:vertical { + background: #c0c0c0; + border-radius: 4px; + min-height: 20px; +} + +/* Handle on hover */ +QScrollArea#recents_scroll_area QScrollBar::handle:vertical:hover { + background: #a0a0a0; +} + +/* Handle when pressed */ +QScrollArea#recents_scroll_area QScrollBar::handle:vertical:pressed { + background: #808080; /* Even darker grey when pressed */ +} + +/* Remove add/sub line buttons (arrows) */ +QScrollArea#recents_scroll_area QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical { + border: none; + background: none; +} + +/* Styling the page area (the track around the handle) */ +QScrollArea#recents_scroll_area QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { + background: none; /* Make the page area transparent, letting the QScrollBar background show through */ +} + +/* Default button style */ +QPushButton#TopButton { + background: #fff; + border-radius: 0px; + border: 1px solid #000000; + color: #000; + font-size: 1px; + padding: 10px; + text-align: center; +} + +/* Hover button style */ +QPushButton#TopButton[hover="true"] { + background: #90AF13; + border: 1px solid #8AB23A; + color: #fff; + font-size: 14px; + font-weight: 600; + padding: 10px; + text-align: center; +} + +/* Pressed button style */ +QPushButton#TopButton[pressed="true"] { + background: #8AB23A; + border: 1px solid #8AB23A; + color: #fff; + font-size: 14px; + font-weight: 600; + padding: 10px; + text-align: center; +} + +/* Dropdown menu style */ +QMenu { + background: #fff; + border: 1px solid #90AF13; + font-size: 14px; + padding: 0px; +} + +QMenu::item { + padding: 8px 16px; + color: #333; + border: none; + margin: 1px; +} + +QMenu::item:selected { + background: #90AF13; + color: #fff; + border-radius: 2px; +} + +QWidget#searchContainer { + border: 2px solid #e1e5e9; + border-radius: 24px; + background: white; + min-height: 48px; + padding: 0px 8px; +} +QWidget#searchContainer[focused="true"] { + border-color: #C8D8A2; +} +QLineEdit#searchInput { + border: none; + background: transparent; + font-size: 16px; + color: #333; +} +QLineEdit#searchInput:focus { + outline: none; + border: none; +} +QLabel#shortcutKey, #lKey { + background: #f5f5f5; + border: 1px solid #ddd; + border-radius: 4px; + color: #666; + font-size: 11px; + font-weight: 600; + padding: 2px 4px; +} +QLabel#plusLabel { + color: #888; + font-size: 12px; + font-weight: 500; +} + +QFrame#projectItem{ + background: #f8f9fa; + border: 1px solid #e2e8f0; + margin: 2px 4px 6px 4px; + border-radius: 10px; +} +QFrame#projectItem:hover{ + background: #D5E49B; + border-color: #9BC53D; +} +QFrame#projectItem[selected="true"] { + background: #D5E49B; + border-color: #9BC53D; +} +QLabel#projectName{ + color: #1a202c; + font-size: 13px; +} +QLabel#submoduleName, +QLabel#dateLabel { + color: #718096; + font-size: 11px; +} + +QLabel#record_icon_label { + font-weight: bold; + color: #1a202c; + font-size: 16px; +} + +QPushButton#recent_proj_btn { + background-color: #ffffff ; + border-radius: 12px; + color: #2d3748 ; + border: 1px solid #cccccc ; + padding: 4px 5px ; + font-size: 10px ; + font-weight: 600 ; +} +QPushButton#recent_proj_btn:hover { + background-color: #f0f4e3 ; + border-color: #9BC53D ; +} +QPushButton#recent_proj_btn:pressed { + background-color: #d6e6be ; +} + +QFrame#moduleItem { + background: #f8f9fa; + border: 1px solid #e2e8f0; + margin: 2px 4px 6px 4px; + border-radius: 10px; +} +QFrame#moduleItem:hover { + background: #D5E49B; + border-color: #9BC53D; +} +QLabel#moduleName { + color: #1a202c; + font-size: 13px; +} +QLabel#subModuleLabel, QLabel#dateLabel { + color: #718096; + font-size: 11px; +} + +QFrame#sectionFrame { + background: #ffffff; + border: 2px solid #e1e5e9; + border-radius: 18px; +} +QLabel#sectionHeading { + font-family: "Calibri", sans-serif; + font-size: 18px; + font-weight: bold; + color: #000000; + background: transparent; + padding: 8px; +} +QWidget#scrollContainer { + background: transparent; + border: none; + margin: 2px 2px 6px 2px; +} +QWidget#scrollContainer_empty { + background: #f8f9fa; + border: 1px solid #e2e8f0; + margin: 2px 2px 6px 2px; + border-radius: 10px; +} +QWidget#SplashScreen_CentralWidget { + border: 2px solid #90af13; + border-radius: 5px; + background-image: url(:/backgrounds/splash_bg.jpg); + background-repeat: no-repeat; + background-position: center; +} +QLabel#splash_loading_label { + background-color: rgba(255,255,255,0.3); +} + +/*=============================================================== + cad view buttons +===============================================================*/ +QPushButton#cad_view_buttons { + background-color: white; + border: 1px solid black; + border-radius: 0; + color: black; + padding: 0; + font-weight: bold; +} +QPushButton#cad_view_buttons:hover { + background-color: #90AF13; + border: 1px solid #90AF13; + color: white; +} +QPushButton#cad_view_buttons:pressed { + background-color: white; + border: 1px solid #90AF13; + color: black; +} +/*===============================================================*/ + +QWidget#output_dock_indicator, +QWidget#input_dock_indicator { + background-color: white; +} + +/*=======================Template-Page===========================*/ + +QWidget#template_page { + background-color: #ffffff; + margin: 0px; + padding: 0px; +} +QWidget#control_btn_widget { + background-color: #F4F4F4; + padding: 0px; +} +QMenuBar#template_page_menu_bar { + background-color: #F4F4F4; + color: #000000; + padding: 0px; +} +QMenuBar#template_page_menu_bar::item { + padding: 5px 10px; + background: transparent; + border-radius: 0px; +} +QMenuBar#template_page_menu_bar::item:selected { + background: #FFFFFF; +} +QMenuBar#template_page_menu_bar::item:pressed { + background: #E8E8E8; +} +QMenuBar#template_page_menu_bar QMenu { + background-color: #FFFFFF; + border: 1px solid #D0D0D0; + border-radius: 4px; + padding: 0px; +} +QMenuBar#template_page_menu_bar QMenu::item { + padding: 5px; + color: #000000; + font-size: 11px; +} +QMenuBar#template_page_menu_bar QMenu::item:selected { + background-color: #E6F0FF; + border-radius: 3px; +} +QMenuBar#template_page_menu_bar QMenu::separator { + height: 1px; + background: #F0F0F0; + margin-left: 2px; + margin-right: 2px; + margin-top: 0px; + margin-bottom: 0px; +} +QMenuBar#template_page_menu_bar QMenu::right-arrow { + width: 8px; + height: 8px; +} +QWidget#toggle_strip { + background-color: #94b816; +} +QPushButton#toggle_strip_button { + background-color: #6c8408; + color: white; + font-size: 12px; + font-weight: bold; + padding: 0px; + border: none; +} +QPushButton#toggle_strip_button:hover { + background-color: #5e7407; +} + +QWidget#cad_custom_selector { + padding: 5px; +} +QWidget#cad_custom_selector QCheckBox { + color: #000000; + margin: 5px; + border-style: solid; + border-width: 1px; + border-color: transparent; +} +QWidget#cad_custom_selector QCheckBox:disabled { + color: #808086; + margin: 5px; + border-style: solid; + border-width: 1px; + border-color: transparent; +} +QWidget#cad_custom_selector QCheckBox:hover { + border-radius: 4px; + border-style: solid; + border-width: 1px; + border-color: transparent; +} + +QWidget#cad_custom_selector QCheckBox::indicator { + width: 12px; + height: 12px; +} + +QWidget#cad_custom_selector QCheckBox::indicator:checked { + border-style: solid; + border-width: 1px; + border-color: qlineargradient( + spread:pad, x1:0.5, y1:1, x2:0.5, y2:0, + stop:0 rgba(120, 120, 120, 255), + stop:1 rgba(180, 180, 180, 255) + ); + background-color: qlineargradient( + spread:pad, x1:0.5, y1:1, x2:0.5, y2:0, + stop:0 rgba(120, 120, 120, 255), + stop:1 rgba(200, 200, 200, 255) + ); +} + +QWidget#cad_custom_selector QCheckBox::indicator:unchecked { + border-style: solid; + border-width: 1px; + border-color: qlineargradient( + spread:pad, x1:0.5, y1:1, x2:0.5, y2:0, + stop:0 rgba(160, 160, 160, 255), + stop:1 rgba(200, 200, 200, 255) + ); + background-color: #ffffff; +} +/*==========floating-navbar=================================*/ +QWidget#floating_btn_container { + background-color: #FFFFFF; + border: 1px solid #90AF13; + border-top-left-radius: 5px; + border-top-right-radius: 5px; + border-bottom-left-radius: 5px; + border-bottom-right-radius: 5px; +} +QPushButton#floating_btn[state="default"] { + background-color: white; + border: 0px; + border-radius: 5px; + margin: 3px; + padding: 1px; +} +QPushButton#floating_btn[state="selected"] { + background-color: #d1f16a; + border-radius: 5px; + border: 0px; + margin: 3px; + padding: 1px; +} +QPushButton#floating_btn[state="active"] { + background-color: #d1f16a; + border-radius: 5px; + border: 0px; + margin: 3px; + padding: 1px; +} +/*=======================Logs-Dock===========================*/ +QWidget#logs_dock QLabel { + background-color: #F2F2F2; + color: #000000; + padding: 3px; + font-weight: bold; + font-size: 12px; +} +QWidget#logs_dock QTextEdit { + background-color: #F8F8F8; + border: 1px solid #D0D0D0; + font-family: 'Courier New', monospace; + font-size: 12px; + padding: 5px; + color: #000000; +} +QWidget#logs_dock QScrollBar:vertical { + background: #E0E0E0; /* Light grey for the scrollbar track */ + width: 8px; + margin: 0px 0px 0px 3px; + border-radius: 2px; +} +QWidget#logs_dock QScrollBar::handle:vertical { + background: #A0A0A0; /* Medium grey for the scrollbar handle */ + min-height: 30px; + border-radius: 2px; +} +QWidget#logs_dock QScrollBar::handle:vertical:hover { + background: #707070; /* Darker grey on hover for the handle */ +} +QWidget#logs_dock QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical { + height: 0px; /* Hides the up/down arrows */ +} +QWidget#logs_dock QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { + background: none; /* Hides the area between handle and arrows */ +} + +/*=======================Input-Dock===========================*/ + +QScrollArea#inputs_vscrollarea, +QScrollArea#outputs_vscrollarea { + border: 1px solid #EFEFEC; + background-color: transparent; + padding: 3px; +} +QScrollArea#inputs_vscrollarea QScrollBar:vertical, +QScrollArea#outputs_vscrollarea QScrollBar:vertical { + background: #E0E0E0; + width: 8px; + margin: 0px 0px 0px 3px; + border-radius: 2px; +} +QScrollArea#inputs_vscrollarea QScrollBar::handle:vertical, +QScrollArea#outputs_vscrollarea QScrollBar::handle:vertical { + background: #A0A0A0; + min-height: 30px; + border-radius: 2px; +} +QScrollArea#inputs_vscrollarea QScrollBar::handle:vertical:hover, +QScrollArea#outputs_vscrollarea QScrollBar::handle:vertical:hover { + background: #707070; +} +QScrollArea#inputs_vscrollarea QScrollBar::add-line:vertical, +QScrollArea#inputs_vscrollarea QScrollBar::sub-line:vertical, +QScrollArea#outputs_vscrollarea QScrollBar::add-line:vertical, +QScrollArea#outputs_vscrollarea QScrollBar::sub-line:vertical { + height: 0px; +} +QScrollArea#inputs_vscrollarea QScrollBar::add-page:vertical, +QScrollArea#inputs_vscrollarea QScrollBar::sub-page:vertical, +QScrollArea#outputs_vscrollarea QScrollBar::add-page:vertical, +QScrollArea#outputs_vscrollarea QScrollBar::sub-page:vertical { + background: none; +} + +#inputs_container{ + background-color: #FFFFFF; +} +#inputs_container QComboBox{ + padding: 2px; + border: 1px solid black; + border-radius: 5px; + background-color: white; + color: black; +} +#inputs_container QComboBox::drop-down{ + subcontrol-origin: padding; + subcontrol-position: top right; + border-left: 0px; +} +#inputs_container QComboBox::down-arrow{ + image: url(:/images/down_arrow_light.png); + width: 15px; + height: 15px; + margin-right: 5px; +} +#inputs_container QComboBox QAbstractItemView{ + background-color: white; + border: 1px solid black; + outline: none; +} +#inputs_container QComboBox QAbstractItemView::item{ + color: black; + background-color: white; + border: none; + border: 1px solid white; + border-radius: 0; + padding: 2px; +} +#inputs_container QComboBox QAbstractItemView::item:hover{ + border: 1px solid #90AF13; + background-color: #90AF13; + color: black; +} +#inputs_container QComboBox QAbstractItemView::item:selected{ + background-color: #90AF13; + color: black; + border: 1px solid #90AF13; +} +#inputs_container QComboBox QAbstractItemView::item:selected:hover{ + background-color: #90AF13; + color: black; + border: 1px solid #94b816; +} +#inputs_container QLineEdit { + padding: 1px 7px; + border: 1px solid #070707; + border-radius: 6px; + background-color: white; + color: #000000; + font-weight: normal; +} +#inputs_container QGroupBox{ + border: 1px solid #90AF13; + border-radius: 4px; + margin-top: 0.8em; + font-weight: bold; +} +#inputs_container QGroupBox::title{ + subcontrol-origin: content; + subcontrol-position: top left; + left: 10px; + padding: 0 4px; + margin-top: -12px; + background-color: white; +} + +QWidget#outputs_group_container, +QWidget#outputs_container{ + background-color: #FFFFFF; +} + +#outputs_container QLineEdit { + padding: 2px 7px; + border: 1px solid #070707; + border-radius: 4px; + background-color: white; + color: #000000; + font-weight: normal; +} +#outputs_container QGroupBox { + border: 1px solid #90AF13; + border-radius: 4px; + margin-top: 0.8em; + font-weight: bold; +} +#outputs_container QGroupBox::title { + subcontrol-origin: content; + subcontrol-position: top left; + left: 10px; + padding: 0 4px; + margin-top: -12px; + background-color: white; +} +#outputs_container QPushButton { + padding: 3px 9px; + background-color: #888; + color: white; + border: 0px; + border-radius: 4px; + min-width: 100px; + max-width: 120px; + font-size: 12px; +} +#outputs_container QPushButton:disabled { + background-color: #cccccc; + color: #888888; +} + +QPushButton#inputs_button, +QPushButton#outputs_button { + background-color: #94b816; + border: 0px; + color: white; + font-weight: bold; + border-radius: 4px; + padding: 7px 14px; +} +QWidget #inputs-leftpanel { + background-color: white; +} + +QWidget #outputs-rightpanel { + background-color: white; +} + +QDialog#custom_material_popup { + background-color: #ffffff; + color: #000000; + border: 1px solid #90AF13; +} +#custom_material_popup QLabel, +QDialog#spacing_capacity_details QLabel, +QDialog#spacing_dialog QLabel { + color: #000000; +} +#custom_material_popup QLineEdit { + background-color: #ffffff; + color: #000000; + border: 1px solid #c0c0c0; + border-radius: 3px; + padding: 2px 4px; +} +#custom_material_popup QLineEdit[readOnly="true"] { + background-color: #f0f0f0; +} +#custom_material_popup QLineEdit:focus { + border: 1px solid #90af13; +} +#custom_material_popup QPushButton { + background-color: #90af13; + color: #ffffff; + padding: 6px 16px; + border-radius: 3px; + border: none; + font-weight: bold; +} +#custom_material_popup QPushButton:hover { + background-color: #7a9a12; +} +#custom_material_popup QPushButton:pressed { + background-color: #5f7a0e; +} + +QScrollArea#inputs_hscrollarea, +QScrollArea#outputs_hscrollarea { + border: none; + background: transparent; +} +QScrollArea#inputs_hscrollarea QScrollBar:horizontal, +QScrollArea#outputs_hscrollarea QScrollBar:horizontal { + background: #E0E0E0; + height: 8px; + margin: 3px 0px 0px 0px; + border-radius: 2px; +} +QScrollArea#inputs_hscrollarea QScrollBar::handle:horizontal, +QScrollArea#outputs_hscrollarea QScrollBar::handle:horizontal { + background: #A0A0A0; + min-width: 30px; + border-radius: 2px; +} +QScrollArea#inputs_hscrollarea QScrollBar::handle:horizontal:hover, +QScrollArea#outputs_hscrollarea QScrollBar::handle:horizontal:hover { + background: #707070; +} +QScrollArea#inputs_hscrollarea QScrollBar::add-line:horizontal, QScrollBar::sub-line:horizontal, +QScrollArea#outputs_hscrollarea QScrollBar::add-line:horizontal, QScrollBar::sub-line:horizontal { + width: 0px; +} +QScrollArea#inputs_hscrollarea QScrollBar::add-page:horizontal, QScrollBar::sub-page:horizontal, +QScrollArea#outputs_hscrollarea QScrollBar::add-page:horizontal, QScrollBar::sub-page:horizontal { + background: none; +} + +/*=======================dialogs===========================*/ +QDialog#spacing_capacity_details QScrollArea{ + background-color: #E0E0E0; + color: #000000; +} +QDialog#spacing_dialog QScrollArea, +QDialog#spacing_dialog QWidget#scroll_widget{ + background-color: #FFFFFF; + color: #000000; +} +QDialog#TutorialsDialog, +QDialog#AskQuestions, +QDialog#AboutOsdagDialog, +QDialog#UpdateDialog, +QDialog#CustomDialog, +QDialog#design_report_popup, +QDialog#customized_input, +QDialog#spacing_dialog{ + background-color: #FFFFFF; + border: 1px solid #90AF13; +} +QDialog#spacing_capacity_details { + background-color: #E0E0E0; + border: 1px solid #90AF13; +} +QWidget#CustomTitleBar { + background-color: #f4f4f4; +} +QWidget#BottomLine { + background-color: #90AF13; +} +QLabel#message_lbl1 { + font-size: 14px; + font-weight: bold; + color: #444444; +} +QLabel#message_lbl2 { + font-size: 12px; + color: #555555; +} +QLabel#TitleLabel { + color: #000000; + padding: 0px; + background: transparent; +} +QLabel#LogoLabel { + background: transparent; + color: #ffffff; + font-size: 14px; +} + +QToolButton#MinimizeButton { + background-color: transparent; + color: #000000; + border: 0px; + border-radius: 0px; + font-size: 16px; + border-radius: 0px; +} + +QToolButton#MinimizeButton:hover { + background-color: #f1f1f1; +} + +QToolButton#MinimizeButton:pressed { + background-color: #a6a6a6; +} + +QToolButton#MaxRestoreButton { + background-color: transparent; + color: #000000; + border: 0px; + border-radius: 0px; + font-size: 16px; + border-radius: 0px; +} + +QToolButton#MaxRestoreButton:hover { + background-color: #f1f1f1; +} + +QToolButton#MaxRestoreButton:pressed { + background-color: #a6a6a6; +} + +QToolButton#CloseButton { + background-color: transparent; + color: #000000; + border: 0px; + border-radius: 0px; + font-size: 16px; + border-radius: 0px; +} + +QToolButton#CloseButton:hover { + background-color: #e74c3c; + color: #ffffff; +} + +QToolButton#CloseButton:pressed { + background-color: #c0392b; +} + +QDialog#AboutOsdagDialog QWidget#ContentWidget, +QDialog#UpdateDialog QWidget#ContentWidget, +QDialog#customized_input QWidget#ContentWidget { + background-color: #ffffff; +} +QDialog#AboutOsdagDialog QTextBrowser, +QDialog#UpdateDialog QTextBrowser { + background-color: #ffffff; + border: 1px solid #e0e0e0; + border-radius: 4px; + font-family: 'Arial', sans-serif; + font-size: 8pt; + padding: 8px; +} +QDialog#AboutOsdagDialog QPushButton, +QDialog#UpdateDialog QPushButton { + background-color: #90AF13; + color: white; + border: none; + border-radius: 5px; + padding: 5px 20px; + font-size: 12px; + font-weight: bold; +} +QDialog#design_report_popup QPushButton, +QDialog#spacing_capacity_details QPushButton { + background-color: white; + color: black; + font-weight: bold; + border-radius: 5px; + border: 1px solid black; + padding: 5px 14px; + text-align: center; +} +QDialog#AboutOsdagDialog QPushButton:hover, +QDialog#UpdateDialog QPushButton:hover { + background-color: #7A9611; +} +QDialog#AboutOsdagDialog QPushButton:pressed, +QDialog#UpdateDialog QPushButton:pressed { + background-color: #6B850F; +} +QDialog#design_report_popup QPushButton { + background-color: #FFFFFF; + color: #000000; + font-weight: bold; + border-radius: 5px; + border: 1px solid #000000; + padding: 5px 14px; + text-align: center; +} +QDialog#design_report_popup QPushButton:hover, +QDialog#spacing_capacity_details QPushButton:hover { + background-color: #90AF13; + border: 1px solid #90AF13; + color: white; +} +QDialog#design_report_popup QPushButton:pressed, +QDialog#spacing_capacity_details QPushButton:pressed { + color: black; + background-color: white; + border: 1px solid black; +} +QDialog#design_report_popup QLineEdit, +QDialog#design_report_popup QTextEdit, +QDialog#spacing_dialog QLineEdit { + padding: 2px 7px; + border: 1px solid #A6A6A6; + border-radius: 4px; + background-color: white; + color: #000000; + font-weight: normal; +} + +QWidget#report_customization_widget QTreeWidget { + border: 1px solid #90AF13; + border-radius: 6px; + background-color: #ffffff; + show-decoration-selected: 1; +} +QWidget#report_customization_widget QTreeWidget::item { + padding: 4px 6px; + background-color: #f9f9f9; + border-bottom: 1px solid #e0e0e0; +} +QWidget#report_customization_widget QTreeWidget::item:selected { + background-color: #d9e9c7; + color: #000000; +} +QWidget#report_customization_widget QTreeWidget::item:hover { + background-color: #eef5e6; +} +QWidget#report_customization_widget QTreeWidget::indicator { + width: 14px; + height: 14px; +} +QWidget#report_customization_widget QTreeWidget::indicator:unchecked { + border: 1px solid #555555; + background: #ffffff; +} +QWidget#report_customization_widget QTreeWidget::indicator:checked { + border: 1px solid #555555; + background: #90AF13; + image: none; +} +QWidget#report_customization_widget QHeaderView::section { + background-color: #e1e1e1; + color: #000000; + padding-left: 5px; + border-radius: 0px; + border-top-left-radius: 6px; + border-top-right-radius: 6px; +} +QWidget#report_customization_widget QTreeWidget QHeaderView { + border-top-left-radius: 6px; + border-top-right-radius: 6px; +} +/*=============customized-inputs======================*/ +QDialog#customized_input QLabel#label, +QDialog#customized_input QLabel#label_2 { + font-weight: bold; + font-size: 13px; + color: #333333; + padding: 2px; +} +QDialog#customized_input QLabel#note_label { + background-color: #f9f9f9; + border: 1px solid #e0e0e0; + border-radius: 5px; + padding: 10px; + color: #555555; + font-size: 12px; +} +QDialog#customized_input QListWidget { + background-color: white; + border: 1px solid #d0d0d0; + border-radius: 5px; + padding: 5px; +} +QDialog#customized_input QListWidget::item { + border-left: 2px solid transparent; + background-color: transparent; + color: #000000; + padding: 3px; + margin: 1px; +} +QDialog#customized_input QListWidget::item:hover { + background-color: #f0f7d0; + border-left-color: #90af13; +} +QDialog#customized_input QListWidget::item:selected { + background-color: #e8f4c8; + color: black; + border-left: 2px solid #90AF13; +} +QDialog#customized_input QListWidget::item:selected:hover { + background-color: #d8e4b8; + border-left-color: #7a9a12; +} +QDialog#customized_input QListWidget::item:disabled { + color: #999999; + background-color: #f5f5f5; +} +QDialog#customized_input QScrollBar:vertical { + background: #f0f0f0; + width: 10px; + margin: 0; + border-radius: 5px; +} +QDialog#customized_input QScrollBar::handle:vertical { + background: #c0c0c0; + min-height: 30px; + border-radius: 5px; +} +QDialog#customized_input QScrollBar::handle:vertical:hover { + background: #90AF13; +} +QDialog#customized_input QPushButton#pushButton, +QDialog#customized_input QPushButton#pushButton_2, +QDialog#customized_input QPushButton#pushButton_3, +QDialog#customized_input QPushButton#pushButton_4, +QDialog#customized_input QPushButton#pushButton_5 { + background-color: #90AF13; + color: white; + font-weight: bold; + font-size: 14px; + border-radius: 5px; + border: none; + padding: 5px; +} +QDialog#customized_input QPushButton#pushButton:hover, +QDialog#customized_input QPushButton#pushButton_2:hover, +QDialog#customized_input QPushButton#pushButton_3:hover, +QDialog#customized_input QPushButton#pushButton_4:hover, +QDialog#customized_input QPushButton#pushButton_5:hover { + background-color: #7a9a12; +} +QDialog#customized_input QPushButton#pushButton:pressed, +QDialog#customized_input QPushButton#pushButton_2:pressed, +QDialog#customized_input QPushButton#pushButton_3:pressed, +QDialog#customized_input QPushButton#pushButton_4:pressed, +QDialog#customized_input QPushButton#pushButton_5:pressed { + background-color: #5f7a0e; +} +QDialog#customized_input QPushButton#pushButton:disabled, +QDialog#customized_input QPushButton#pushButton_2:disabled, +QDialog#customized_input QPushButton#pushButton_3:disabled, +QDialog#customized_input QPushButton#pushButton_4:disabled, +QDialog#customized_input QPushButton#pushButton_5:disabled { + background-color: #d0d0d0; + color: #888888; +} +/*=======================Additional-Inputs=======================================*/ +QDialog#AdditionalInputs{ + background-color: #FFFFFF; + border: 1px solid #90AF13; +} +QWidget#TableWidget QTabWidget QWidget{ + background-color: #ffffff; +} +QWidget#TableWidget QTabWidget > QWidget > QWidget > QWidget{ + background-color: #ffffff; + border: 1px solid #D0D0D0; +} +#AdditionalInputs QTabBar::tab { + background: #F4F4F4; + border: 1px solid #d9d7d7; + padding: 6px 18px 6px 18px; + color: #000000; + margin-left: 0px; +} +#AdditionalInputs QTabBar::tab:selected { + background: #ffffff; + color: #000000; + border: 1px solid #000000; + border-bottom: 1px solid #ffffff; + padding: 6px 18px 6px 18px; +} +#AdditionalInputs QTabBar::tab:hover { + border: 1px solid #000000; +} +/* Additional Input Button */ +QDialog#AdditionalInputs QPushButton { + background-color: white; + color: black; + font-weight: bold; + border-radius: 5px; + border: 1px solid black; + padding: 5px 14px; + text-align: center; +} +QDialog#AdditionalInputs QPushButton:hover { + background-color: #90AF13; + border: 1px solid #90AF13; + color: white; +} +QDialog#AdditionalInputs QPushButton:pressed { + color: black; + background-color: white; + border: 1px solid black; +} +QDialog#AdditionalInputs QLineEdit { + padding: 2px 7px; + border: 1px solid #A6A6A6; + border-radius: 4px; + background-color: white; + color: #000000; + font-weight: normal; +} +QDialog#AdditionalInputs QComboBox{ + padding: 2px 7px; + border: 1px solid #A6A6A6; + border-radius: 4px; + background-color: white; + color: black; +} +QDialog#AdditionalInputs QComboBox::drop-down{ + subcontrol-origin: padding; + subcontrol-position: top right; + border-left: 0px; +} +QDialog#AdditionalInputs QComboBox::down-arrow{ + image: url(:/images/down_arrow_light.png); + width: 15px; + height: 15px; + margin-right: 5px; +} +QDialog#AdditionalInputs QComboBox QAbstractItemView{ + background-color: white; + border: 1px solid black; + outline: none; +} +QDialog#AdditionalInputs QComboBox QAbstractItemView::item{ + color: black; + background-color: white; + border: none; + border: 1px solid white; + border-radius: 0; + padding: 2px; +} +QDialog#AdditionalInputs QComboBox QAbstractItemView::item:hover{ + border: 1px solid #90AF13; + background-color: #90AF13; + color: black; +} +QDialog#AdditionalInputs QComboBox QAbstractItemView::item:selected{ + background-color: #90AF13; + color: black; + border: 1px solid #90AF13; +} +QDialog#AdditionalInputs QComboBox QAbstractItemView::item:selected:hover{ + background-color: #90AF13; + color: black; + border: 1px solid #94b816; +} +/* Plate-Girder Bounds Dialog */ +QDialog#BoundsSelectorDialog { + background-color: #FFFFFF; + border: 1px solid #90AF13; + border-radius: 0px; +} +QDialog#BoundsSelectorDialog QLineEdit { + background-color: #FFFFFF; + border: 1px solid #CCCCCC; + border-radius: 4px; + padding: 5px 8px; + font-size: 12px; + color: #333333; +} +QDialog#BoundsSelectorDialog QLineEdit:hover { + border: 1px solid #999999; +} +QDialog#BoundsSelectorDialog QLineEdit:focus { + border: 1px solid #90AF13; + background-color: #F5FFF9; +} +QDialog#BoundsSelectorDialog QLabel { + color: #000000; +} \ No newline at end of file diff --git a/src/osbridge/resources/vectors/arrow_down_dark.svg b/src/osbridge/resources/vectors/arrow_down_dark.svg new file mode 100644 index 00000000..2c10661b --- /dev/null +++ b/src/osbridge/resources/vectors/arrow_down_dark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/osbridge/resources/vectors/arrow_down_light.svg b/src/osbridge/resources/vectors/arrow_down_light.svg new file mode 100644 index 00000000..52e93659 --- /dev/null +++ b/src/osbridge/resources/vectors/arrow_down_light.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/osbridge/resources/vectors/arrow_up_dark.svg b/src/osbridge/resources/vectors/arrow_up_dark.svg new file mode 100644 index 00000000..34f7193e --- /dev/null +++ b/src/osbridge/resources/vectors/arrow_up_dark.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/osbridge/resources/vectors/arrow_up_light.svg b/src/osbridge/resources/vectors/arrow_up_light.svg new file mode 100644 index 00000000..494d7624 --- /dev/null +++ b/src/osbridge/resources/vectors/arrow_up_light.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/osbridge/resources/vectors/design.svg b/src/osbridge/resources/vectors/design.svg new file mode 100644 index 00000000..a5085160 --- /dev/null +++ b/src/osbridge/resources/vectors/design.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/osbridge/resources/vectors/design_report.svg b/src/osbridge/resources/vectors/design_report.svg new file mode 100644 index 00000000..5c224e91 --- /dev/null +++ b/src/osbridge/resources/vectors/design_report.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/osbridge/resources/vectors/lock_close.svg b/src/osbridge/resources/vectors/lock_close.svg new file mode 100644 index 00000000..8495792d --- /dev/null +++ b/src/osbridge/resources/vectors/lock_close.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/osbridge/resources/vectors/lock_open.svg b/src/osbridge/resources/vectors/lock_open.svg new file mode 100644 index 00000000..5f22789a --- /dev/null +++ b/src/osbridge/resources/vectors/lock_open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/osbridge/resources/vectors/save.svg b/src/osbridge/resources/vectors/save.svg new file mode 100644 index 00000000..c86072a9 --- /dev/null +++ b/src/osbridge/resources/vectors/save.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/osbridge/template_page.py b/src/osbridge/template_page.py index 456306d4..9165d857 100644 --- a/src/osbridge/template_page.py +++ b/src/osbridge/template_page.py @@ -9,20 +9,17 @@ QMenuBar, QSplitter, QSizePolicy, - QPushButton, - QGroupBox, - QLineEdit, - QComboBox, - QCheckBox, - QScrollArea, - QFrame, ) -from PySide6.QtCore import Qt, QSize +from PySide6.QtCore import Qt, QFile, QTextStream from PySide6.QtGui import QIcon -from input_dock import InputDock -from backend import BackendOsBridge -from common import * +# Import resources to register them +from osbridge.resources import resources_rc + +from osbridge.ui.input_dock import InputDock +from osbridge.ui.output_dock import OutputDock +from osbridge.backend.backend import BackendOsBridge +from osbridge.backend.common import * class DummyCADWidget(QWidget): @@ -37,7 +34,7 @@ def __init__(self): """ QLabel { background-color: #f0f0f0; - border: 2px dashed #999; + border: 1px solid #999; padding: 40px; font-size: 18px; color: #666; @@ -47,351 +44,6 @@ def __init__(self): layout.addWidget(label) -class OutputDock(QWidget): - """Output dock with collapsible design controls and scrollable layout.""" - - def __init__(self): - super().__init__() - self.setStyleSheet( - """ - QWidget { - background-color: white; - } - """ - ) - self.init_ui() - - def init_ui(self): - from input_dock import NoScrollComboBox, apply_field_style - - main_layout = QVBoxLayout(self) - main_layout.setContentsMargins(8, 8, 8, 8) - main_layout.setSpacing(10) - - title_bar = QWidget() - title_bar.setStyleSheet( - """ - QWidget { - background-color: #90AF13; - border-radius: 4px; - } - """ - ) - title_bar.setFixedHeight(35) - title_layout = QHBoxLayout(title_bar) - title_layout.setContentsMargins(12, 0, 12, 0) - - title_label = QLabel("Output Dock") - title_label.setStyleSheet( - """ - QLabel { - color: white; - font-size: 12px; - font-weight: bold; - } - """ - ) - title_layout.addWidget(title_label) - title_layout.addStretch() - main_layout.addWidget(title_bar) - - scroll = QScrollArea() - scroll.setWidgetResizable(True) - scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) - scroll.setStyleSheet("QScrollArea { border: none; background: white; }") - - scroll_content = QWidget() - scroll_layout = QVBoxLayout(scroll_content) - scroll_layout.setContentsMargins(0, 0, 0, 0) - scroll_layout.setSpacing(10) - - results_group = QGroupBox("Analysis Results") - results_group.setStyleSheet( - """ - QGroupBox { - font-weight: bold; - font-size: 11px; - color: #333; - border: 1px solid #d0d0d0; - border-radius: 4px; - margin-top: 8px; - padding-top: 12px; - background-color: white; - } - QGroupBox::title { - subcontrol-origin: margin; - subcontrol-position: top left; - left: 8px; - padding: 0 4px; - background-color: white; - } - """ - ) - results_layout = QVBoxLayout(results_group) - results_layout.setContentsMargins(10, 8, 10, 10) - results_layout.setSpacing(8) - - member_row = QHBoxLayout() - member_label = QLabel("Member:") - member_label.setStyleSheet("font-size: 10px; color: #333; font-weight: normal;") - member_label.setMinimumWidth(100) - self.member_combo = NoScrollComboBox() - self.member_combo.addItems(["All"]) - apply_field_style(self.member_combo) - member_row.addWidget(member_label) - member_row.addWidget(self.member_combo) - results_layout.addLayout(member_row) - - load_combo_row = QHBoxLayout() - load_combo_label = QLabel("Load Combination:") - load_combo_label.setStyleSheet("font-size: 10px; color: #333; font-weight: normal;") - load_combo_label.setMinimumWidth(100) - self.load_combo = NoScrollComboBox() - self.load_combo.addItems(["Envelope"]) - apply_field_style(self.load_combo) - load_combo_row.addWidget(load_combo_label) - load_combo_row.addWidget(self.load_combo) - results_layout.addLayout(load_combo_row) - - forces_grid = QHBoxLayout() - forces_grid.setSpacing(8) - - col1 = QVBoxLayout() - for text in ["Fx", "Mx", "Dx"]: - cb = QCheckBox(text) - cb.setStyleSheet("font-size: 10px; color: #333;") - col1.addWidget(cb) - col2 = QVBoxLayout() - for text in ["Fy", "My", "Dy"]: - cb = QCheckBox(text) - cb.setStyleSheet("font-size: 10px; color: #333;") - col2.addWidget(cb) - col3 = QVBoxLayout() - for text in ["Fz", "Mz", "Dz"]: - cb = QCheckBox(text) - cb.setStyleSheet("font-size: 10px; color: #333;") - col3.addWidget(cb) - forces_grid.addLayout(col1) - forces_grid.addLayout(col2) - forces_grid.addLayout(col3) - results_layout.addLayout(forces_grid) - - display_label = QLabel("Display Options:") - display_label.setStyleSheet("font-size: 10px; color: #333; font-weight: normal; margin-top: 4px;") - results_layout.addWidget(display_label) - - display_row = QHBoxLayout() - display_row.setSpacing(12) - for text in ["Max", "Min"]: - cb = QCheckBox(text) - cb.setStyleSheet("font-size: 10px; color: #333;") - display_row.addWidget(cb) - display_row.addStretch() - results_layout.addLayout(display_row) - - utilization_check = QCheckBox("Controlling Utilization Ratio") - utilization_check.setStyleSheet("font-size: 10px; color: #333;") - results_layout.addWidget(utilization_check) - - scroll_layout.addWidget(results_group) - - design_group = QGroupBox("Design") - design_group.setStyleSheet( - """ - QGroupBox { - font-weight: bold; - font-size: 11px; - color: #333; - border: 1px solid #d0d0d0; - border-radius: 4px; - margin-top: 8px; - padding-top: 12px; - background-color: white; - } - QGroupBox::title { - subcontrol-origin: margin; - subcontrol-position: top left; - left: 8px; - padding: 0 4px; - background-color: white; - } - """ - ) - design_layout = QVBoxLayout(design_group) - design_layout.setContentsMargins(10, 8, 10, 10) - design_layout.setSpacing(8) - - base_dir = os.path.dirname(os.path.abspath(__file__)) - svg_down = os.path.join(base_dir, "dropdown_down.svg").replace("\\", "/") - svg_up = os.path.join(base_dir, "dropdown_up.svg").replace("\\", "/") - - super_frame = QFrame() - super_frame.setStyleSheet( - """ - QFrame { - border: 1px solid #e0e0e0; - border-radius: 4px; - background: white; - } - """ - ) - super_layout = QVBoxLayout(super_frame) - super_layout.setContentsMargins(8, 6, 8, 6) - super_layout.setSpacing(6) - - super_header = QHBoxLayout() - super_title = QLabel("Superstructure") - super_title.setStyleSheet("font-size: 10px; font-weight: bold; color: #333;") - super_header.addWidget(super_title) - super_header.addStretch() - - super_toggle = QPushButton() - super_toggle.setCheckable(True) - super_toggle.setChecked(True) - super_toggle.setIcon(QIcon(svg_up)) - super_toggle.setIconSize(QSize(14, 14)) - super_toggle.setStyleSheet( - """ - QPushButton { - background: transparent; - border: none; - padding: 2px; - } - """ - ) - super_header.addWidget(super_toggle) - super_layout.addLayout(super_header) - - super_body = QWidget() - super_body_layout = QVBoxLayout(super_body) - super_body_layout.setContentsMargins(0, 0, 0, 0) - super_body_layout.setSpacing(6) - - for text in ["Steel Design", "Deck Design"]: - btn = QPushButton(text) - btn.setStyleSheet( - """ - QPushButton { - background-color: white; - color: #333; - border: 1px solid #b0b0b0; - border-radius: 3px; - padding: 8px; - font-size: 10px; - font-weight: normal; - text-align: center; - } - QPushButton:hover { - background-color: #f5f5f5; - } - """ - ) - super_body_layout.addWidget(btn) - - super_layout.addWidget(super_body) - - def toggle_super(checked: bool): - super_body.setVisible(checked) - super_toggle.setIcon(QIcon(svg_up if checked else svg_down)) - - super_toggle.toggled.connect(toggle_super) - design_layout.addWidget(super_frame) - - sub_frame = QFrame() - sub_frame.setStyleSheet( - """ - QFrame { - border: 1px solid #e0e0e0; - border-radius: 4px; - background: white; - } - """ - ) - sub_layout = QVBoxLayout(sub_frame) - sub_layout.setContentsMargins(8, 6, 8, 6) - sub_layout.setSpacing(6) - - sub_header = QHBoxLayout() - sub_title = QLabel("Substructure") - sub_title.setStyleSheet("font-size: 10px; font-weight: bold; color: #333;") - sub_header.addWidget(sub_title) - sub_header.addStretch() - - sub_toggle = QPushButton() - sub_toggle.setCheckable(True) - sub_toggle.setChecked(True) - sub_toggle.setIcon(QIcon(svg_up)) - sub_toggle.setIconSize(QSize(14, 14)) - sub_toggle.setStyleSheet( - """ - QPushButton { - background: transparent; - border: none; - padding: 2px; - } - """ - ) - sub_header.addWidget(sub_toggle) - sub_layout.addLayout(sub_header) - - sub_body = QWidget() - sub_body_layout = QVBoxLayout(sub_body) - sub_body_layout.setContentsMargins(0, 0, 0, 0) - sub_body_layout.setSpacing(0) - sub_layout.addWidget(sub_body) - - def toggle_sub(checked: bool): - sub_body.setVisible(checked) - sub_toggle.setIcon(QIcon(svg_up if checked else svg_down)) - - sub_toggle.toggled.connect(toggle_sub) - design_layout.addWidget(sub_frame) - - scroll_layout.addWidget(design_group) - scroll_layout.addStretch() - - scroll.setWidget(scroll_content) - main_layout.addWidget(scroll) - - results_btn = QPushButton("Generate Results Table") - results_btn.setStyleSheet( - """ - QPushButton { - background-color: #90AF13; - color: white; - border: none; - border-radius: 4px; - padding: 10px; - font-size: 11px; - font-weight: bold; - } - QPushButton:hover { - background-color: #7a9a12; - } - """ - ) - main_layout.addWidget(results_btn) - - report_btn = QPushButton("Generate Report") - report_btn.setStyleSheet( - """ - QPushButton { - background-color: #90AF13; - color: white; - border: none; - border-radius: 4px; - padding: 10px; - font-size: 11px; - font-weight: bold; - } - QPushButton:hover { - background-color: #7a9a12; - } - """ - ) - main_layout.addWidget(report_btn) - - class DummyLogDock(QWidget): """Placeholder for log dock.""" @@ -466,58 +118,54 @@ def init_ui(self): body_layout.setContentsMargins(0, 0, 0, 0) body_layout.setSpacing(0) - splitter = QSplitter(Qt.Horizontal, body_widget) + # Main horizontal splitter + main_splitter = QSplitter(Qt.Horizontal, body_widget) + # Input dock input_dock = InputDock(backend=self.backend, parent=self) - input_dock.setMinimumWidth(300) + input_dock.setMinimumWidth(380) input_dock.setMaximumWidth(450) - splitter.addWidget(input_dock) - - central_widget = QWidget() - central_layout = QVBoxLayout(central_widget) - central_layout.setContentsMargins(0, 0, 0, 0) - central_layout.setSpacing(0) + main_splitter.addWidget(input_dock) + # CAD widget cad_widget = DummyCADWidget() - central_layout.addWidget(cad_widget, 3) - - logs_dock = DummyLogDock() - central_layout.addWidget(logs_dock, 1) - splitter.addWidget(central_widget) + main_splitter.addWidget(cad_widget) + # Output dock output_dock = OutputDock() - output_dock.setMinimumWidth(250) - output_dock.setMaximumWidth(350) - splitter.addWidget(output_dock) + output_dock.setMinimumWidth(350) + output_dock.setMaximumWidth(450) + main_splitter.addWidget(output_dock) - body_layout.addWidget(splitter) + body_layout.addWidget(main_splitter) - splitter.setStretchFactor(0, 0) - splitter.setStretchFactor(1, 1) - splitter.setStretchFactor(2, 0) + # Set stretch factors for main splitter + main_splitter.setStretchFactor(0, 0) # Input dock - fixed + main_splitter.setStretchFactor(1, 1) # Central area - expandable + main_splitter.setStretchFactor(2, 0) # Output dock - fixed - input_dock_width = 240 - output_dock_width = 270 + # Set initial sizes + input_dock_width = 350 + output_dock_width = 280 total_width = self.width() if self.width() > 0 else 1200 central_width = max(500, total_width - input_dock_width - output_dock_width) - splitter.setSizes([input_dock_width, central_width, output_dock_width]) + main_splitter.setSizes([input_dock_width, central_width, output_dock_width]) main_v_layout.addWidget(body_widget) - self.splitter = splitter + # Store references + self.main_splitter = main_splitter self.input_dock = input_dock self.output_dock = output_dock self.cad_widget = cad_widget - self.logs_dock = logs_dock def main(): - app = QApplication(sys.argv) - window = CustomWindow("Fin Plate Connection - Test", BackendOsBridge) - window.resize(1200, 800) + app = QApplication(sys.argv) + window = CustomWindow("Osdag Bridge", BackendOsBridge) + window.showMaximized() window.show() sys.exit(app.exec()) - if __name__ == "__main__": - main() + main() \ No newline at end of file diff --git a/src/osbridge/ui/__init__.py b/src/osbridge/ui/__init__.py new file mode 100644 index 00000000..a1459dfc --- /dev/null +++ b/src/osbridge/ui/__init__.py @@ -0,0 +1,2 @@ +# UI package + diff --git a/src/osbridge/ui/__pycache__/__init__.cpython-313.pyc b/src/osbridge/ui/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..feb9c0ae30f0fa85e9b0cdc14d51d7b036ebd1d2 GIT binary patch literal 178 zcmey&%ge<81RB$nGx>n@V-N=h7@>^MEI`IohI9r^M!%H|MNB~6XOPsbKxeC%(Bjmh z;+Wiw;?j&5|GZR}qRg_?7?;%I?2`O~82@6YqRf=^)R_F@l*IJ-BnYRtC>baK<&lIYq;;_lhPbtkwwJTx;+5mDvF^KVznURsPh#ANN0O^)59{>OV literal 0 HcmV?d00001 diff --git a/src/osbridge/ui/__pycache__/additional_inputs.cpython-313.pyc b/src/osbridge/ui/__pycache__/additional_inputs.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0ffc8db4eb1ea5de87c8fcfb39c7392f97f09f72 GIT binary patch literal 81031 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 literal 0 HcmV?d00001 diff --git a/src/osbridge/ui/__pycache__/input_dock.cpython-313.pyc b/src/osbridge/ui/__pycache__/input_dock.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3d73a355ee84497e07411aa4fa8577e3a61b551c GIT binary patch literal 52837 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 YIb3Tm|MF0v64tlx75?1D0t}e{7u50l?f?J) literal 0 HcmV?d00001 diff --git a/src/osbridge/ui/__pycache__/output_dock.cpython-313.pyc b/src/osbridge/ui/__pycache__/output_dock.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..38de5e87922efc2dff150a05ad127e301f07619e GIT binary patch literal 16975 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 F{{d0AmInX; literal 0 HcmV?d00001 diff --git a/src/osbridge/additional_inputs.py b/src/osbridge/ui/additional_inputs.py similarity index 98% rename from src/osbridge/additional_inputs.py rename to src/osbridge/ui/additional_inputs.py index 651ab129..425a074f 100644 --- a/src/osbridge/additional_inputs.py +++ b/src/osbridge/ui/additional_inputs.py @@ -13,18 +13,13 @@ from PySide6.QtCore import Qt, Signal from PySide6.QtGui import QDoubleValidator, QIntValidator -from common import * +from osbridge.backend.common import * def get_combobox_style(): - """Return the common stylesheet for dropdowns with the SVG icon from file.""" - # Get the paths to the down/up arrow SVG files - current_dir = os.path.dirname(os.path.abspath(__file__)) - svg_down = os.path.join(current_dir, "dropdown_down.svg").replace("\\", "/") - svg_up = os.path.join(current_dir, "dropdown_up.svg").replace("\\", "/") - - return f""" - QComboBox {{ + """Return the common stylesheet for dropdowns with the SVG icon from resources.""" + return """ + QComboBox { padding: 6px 42px 6px 14px; border: 1px solid #b8b8b8; border-radius: 8px; @@ -32,14 +27,14 @@ def get_combobox_style(): color: #2b2b2b; font-size: 12px; min-height: 34px; - }} - QComboBox:hover {{ + } + QComboBox:hover { border: 1px solid #909090; - }} - QComboBox:focus {{ + } + QComboBox:focus { border: 1px solid #4a7ba7; - }} - QComboBox::drop-down {{ + } + QComboBox::drop-down { subcontrol-origin: padding; subcontrol-position: center right; width: 26px; @@ -48,18 +43,18 @@ def get_combobox_style(): border-radius: 13px; background: transparent; right: 8px; - }} - QComboBox::down-arrow {{ - image: url({svg_down}); + } + QComboBox::down-arrow { + image: url(:/vectors/arrow_down_light.svg); width: 16px; height: 16px; - }} + } /* when popup is open show the up arrow */ - QComboBox::down-arrow:on {{ - image: url({svg_up}); + QComboBox::down-arrow:on { + image: url(:/vectors/arrow_up_light.svg); width: 16px; height: 16px; - }} + } QComboBox QAbstractItemView {{ border: 1px solid #b8b8b8; background: #ffffff; diff --git a/src/osbridge/ui/custom_buttons.py b/src/osbridge/ui/custom_buttons.py new file mode 100644 index 00000000..6588616c --- /dev/null +++ b/src/osbridge/ui/custom_buttons.py @@ -0,0 +1,71 @@ +""" +Custom button widgets for Osdag GUI. +Includes menu and action buttons with custom styles. +""" +from PySide6.QtWidgets import ( + QWidget, QPushButton, QVBoxLayout, QHBoxLayout, QApplication, QGridLayout, + QLabel, QMainWindow, QSizePolicy, QFrame +) +from PySide6.QtSvgWidgets import QSvgWidget +from PySide6.QtCore import Qt, Signal, QSize, QEvent, QRect, QPropertyAnimation, QEasingCurve +from PySide6.QtGui import QFont, QIcon, QPainter + +class DockCustomButton(QPushButton): + def __init__(self, text: str, icon_path: str, parent=None): + super().__init__(parent) + self.setCursor(Qt.PointingHandCursor) + self.setObjectName("dock_custom_button") + self.setStyleSheet(""" + QPushButton { + background-color: #90AF13; + color: white; + border: none; + border-radius: 4px; + padding: 10px; + font-size: 11px; + font-weight: bold; + } + QPushButton:hover { + background-color: #7a9a12; + } + """) + + # Layout for icons and text + layout = QHBoxLayout(self) + layout.setContentsMargins(10, 0, 10, 0) + layout.setSpacing(0) + + # Left icon + left_icon = QSvgWidget() + left_icon.load(icon_path) + left_icon.setFixedSize(18, 18) + left_icon.setObjectName("button_icon") + left_icon.setStyleSheet(""" + QSvgWidget { + background: transparent; + } + """) + layout.addWidget(left_icon) + + # Center text + text_label = QLabel(text) + text_label.setAlignment(Qt.AlignCenter) + text_label.setObjectName("button_label") + text_label.setStyleSheet(""" + QLabel { + background: transparent; + color: white; + } + """) + layout.addWidget(text_label) + + layout.setAlignment(Qt.AlignVCenter) + self.setLayout(layout) + + # Calculate minimum width to prevent overlap + text_width = text_label.sizeHint().width() + icon_width = 18 + margins = layout.contentsMargins().left() + layout.contentsMargins().right() + padding = 20 + min_width = text_width + icon_width + margins + padding + self.setMinimumWidth(min_width) \ No newline at end of file diff --git a/src/osbridge/input_dock.py b/src/osbridge/ui/input_dock.py similarity index 77% rename from src/osbridge/input_dock.py rename to src/osbridge/ui/input_dock.py index 56568e04..d776e579 100644 --- a/src/osbridge/input_dock.py +++ b/src/osbridge/ui/input_dock.py @@ -7,142 +7,108 @@ from PySide6.QtCore import Qt, QRegularExpression, QSize from PySide6.QtGui import QPixmap, QDoubleValidator, QRegularExpressionValidator, QIcon from PySide6.QtSvgWidgets import * -from common import * -from additional_inputs import AdditionalInputsWidget +from osbridge.backend.common import * +from osbridge.ui.additional_inputs import AdditionalInputsWidget +from osbridge.ui.custom_buttons import DockCustomButton class NoScrollComboBox(QComboBox): def wheelEvent(self, event): - event.ignore() - + event.ignore() # Prevent changing selection on scroll def apply_field_style(widget): widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) widget.setMinimumHeight(28) if isinstance(widget, QComboBox): - # Get the paths to the down/up arrow SVGs - current_dir = os.path.dirname(os.path.abspath(__file__)) - svg_down = os.path.join(current_dir, "dropdown_down.svg").replace("\\", "/") - svg_up = os.path.join(current_dir, "dropdown_up.svg").replace("\\", "/") - - style = f""" - QComboBox {{ - padding: 4px 8px; - padding-right: 30px; - border: 1px solid #b0b0b0; - border-radius: 3px; - background-color: white; - color: black; - min-height: 24px; - }} - QComboBox::drop-down {{ - subcontrol-origin: padding; - subcontrol-position: center right; - width: 22px; - height: 22px; - border: 1px solid #606060; - background-color: transparent; - border-radius: 11px; - right: 5px; - }} - QComboBox::down-arrow {{ - image: url({svg_down}); - width: 16px; - height: 16px; - }} - /* show up arrow when popup is open */ - QComboBox::down-arrow:on {{ - image: url({svg_up}); - width: 16px; - height: 16px; - }} - QComboBox:hover {{ - border: 1px solid #909090; - }} - QComboBox:focus {{ - border: 1px solid #4a7ba7; - }} - QComboBox QAbstractItemView {{ - background-color: white; - border: 1px solid #b0b0b0; - outline: none; - selection-background-color: #e8f4ff; - }} - QComboBox QAbstractItemView::item {{ - color: black; - background-color: white; - padding: 5px; - min-height: 24px; - }} - QComboBox QAbstractItemView::item:hover {{ - background-color: #e8f4ff; - color: black; - }} + style = """ + QComboBox{ + padding: 2px; + border: 1px solid black; + border-radius: 5px; + background-color: white; + color: black; + } + QComboBox::drop-down{ + subcontrol-origin: padding; + subcontrol-position: top right; + border-left: 0px; + } + QComboBox::down-arrow{ + image: url(:/vectors/arrow_down_light.svg); + width: 20px; + height: 20px; + margin-right: 8px; + } + QComboBox::down-arrow:on { + image: url(:/vectors/arrow_up_light.svg); + width: 20px; + height: 20px; + margin-right: 8px; + } + QComboBox QAbstractItemView{ + background-color: white; + border: 1px solid black; + outline: none; + } + QComboBox QAbstractItemView::item{ + color: black; + background-color: white; + border: none; + border: 1px solid white; + border-radius: 0; + padding: 2px; + } + QComboBox QAbstractItemView::item:hover{ + border: 1px solid #90AF13; + background-color: #90AF13; + color: black; + } + QComboBox QAbstractItemView::item:selected{ + background-color: #90AF13; + color: black; + border: 1px solid #90AF13; + } + QComboBox QAbstractItemView::item:selected:hover{ + background-color: #90AF13; + color: black; + border: 1px solid #94b816; + } """ widget.setStyleSheet(style) elif isinstance(widget, QLineEdit): widget.setStyleSheet(""" - QLineEdit { - padding: 4px 8px; - border: 1px solid #b0b0b0; - border-radius: 3px; - background-color: white; - color: black; - min-height: 24px; - } - QLineEdit:hover { - border: 1px solid #909090; - } - QLineEdit:focus { - border: 1px solid #4a7ba7; - } - QLineEdit:disabled { - background-color: #f5f5f5; - color: #999; - } + QLineEdit { + padding: 1px 7px; + border: 1px solid #070707; + border-radius: 6px; + background-color: white; + color: #000000; + font-weight: normal; + } """) - -def style_main_buttons(): - return """ - QPushButton { - background-color: #90AF13; - color: white; - font-weight: bold; - border: 1px solid #7a9611; - border-radius: 3px; - padding: 6px 16px; - min-height: 28px; - } - QPushButton:hover { - background-color: #7a9a12; - } - QPushButton:pressed { - background-color: #6a8a10; - } - """ - - def create_group_box(title): """Create a styled group box""" group_box = QGroupBox(title) group_box.setStyleSheet(""" QGroupBox { font-weight: bold; - font-size: 11px; + font-size: 12px; color: #333; - border: 1px solid #d0d0d0; - border-radius: 5px; - margin-top: 10px; - padding-top: 10px; - background-color: #fafafa; + border: 1px solid #90AF13; + border-radius: 4px; + margin-top: 0.8em; + padding: 10px; } QGroupBox::title { subcontrol-origin: margin; subcontrol-position: top left; - padding: 0 5px; + left: 8px; + padding: 0 4px; + margin-top: 4px; background-color: white; + color: #333; } """) return group_box @@ -154,7 +120,13 @@ def create_form_row(label_text, widget, tooltip=None): row.setSpacing(10) label = QLabel(label_text) - label.setStyleSheet("font-size: 10px; color: #555; font-weight: normal;") + label.setStyleSheet(""" + QLabel { + color: #000000; + font-size: 12px; + background: transparent; + } + """) label.setMinimumWidth(140) label.setMaximumWidth(140) @@ -201,19 +173,21 @@ def __init__(self, backend, parent): toggle_layout.setSpacing(0) toggle_layout.setAlignment(Qt.AlignVCenter | Qt.AlignLeft) - self.toggle_btn = QPushButton("<") + self.toggle_btn = QPushButton("❮") + self.toggle_btn.setCursor(Qt.CursorShape.PointingHandCursor) self.toggle_btn.setFixedSize(6, 60) self.toggle_btn.setToolTip("Hide panel") self.toggle_btn.setStyleSheet(""" QPushButton { - background-color: #7a9a12; + background-color: #6c8408; color: white; font-size: 12px; font-weight: bold; + padding: 0px; border: none; } QPushButton:hover { - background-color: #6a8a10; + background-color: #5e7407; } """) toggle_layout.addStretch() @@ -244,7 +218,7 @@ def on_project_location_changed(self, text): custom_location, ok = QInputDialog.getText( self, "Custom Location", - "Enter city name for load calculations:", + "Enter city name for load calculations", QLineEdit.Normal, "" ) @@ -337,7 +311,7 @@ def show_project_location_dialog(self): coords_row.addStretch() - lat_label = QLabel("Latitude (°):") + lat_label = QLabel("Latitude (°)") lat_label.setStyleSheet("font-size: 11px;") coords_row.addWidget(lat_label) @@ -347,7 +321,7 @@ def show_project_location_dialog(self): apply_field_style(self.latitude_input) coords_row.addWidget(self.latitude_input) - lng_label = QLabel("Longitude (°):") + lng_label = QLabel("Longitude (°)") lng_label.setStyleSheet("font-size: 11px;") coords_row.addWidget(lng_label) @@ -486,7 +460,7 @@ def show_project_location_dialog(self): results_section = QVBoxLayout() results_section.setSpacing(8) - results_title = QLabel("IRC 6 (2017) Values:") + results_title = QLabel("IRC 6 (2017) Values") results_title.setStyleSheet("font-size: 12px; font-weight: bold; color: #4CAF50;") results_section.addWidget(results_title) @@ -543,12 +517,26 @@ def show_project_location_dialog(self): btn_layout.addStretch() ok_btn = QPushButton("OK") - ok_btn.setStyleSheet(style_main_buttons()) + ok_btn.setCursor(Qt.CursorShape.PointingHandCursor) + ok_btn.setStyleSheet(""" + QPushButton { + background-color: white; + color: #333; + border: 1px solid #c0c0c0; + border-radius: 3px; + padding: 6px 16px; + min-height: 28px; + } + QPushButton:hover { + background-color: #f5f5f5; + } + """) ok_btn.setMinimumWidth(100) ok_btn.clicked.connect(dialog.accept) btn_layout.addWidget(ok_btn) cancel_btn = QPushButton("Cancel") + cancel_btn.setCursor(Qt.CursorShape.PointingHandCursor) cancel_btn.setStyleSheet(""" QPushButton { background-color: white; @@ -627,43 +615,68 @@ def build_left_panel(self, field_list): top_bar.setContentsMargins(0, 0, 0, 15) input_dock_btn = QPushButton("Basic Inputs") - input_dock_btn.setStyleSheet(style_main_buttons()) + input_dock_btn.setStyleSheet(""" + QPushButton { + background-color: #90AF13; + color: white; + font-weight: bold; + font-size: 13px; + border: none; + border-radius: 4px; + padding: 7px 20px; + min-width: 80px; + } + """) input_dock_btn.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) top_bar.addWidget(input_dock_btn) - # Get lock icon path - current_dir = os.path.dirname(os.path.abspath(__file__)) - lock_icon_path = os.path.join(current_dir, "lock.svg") - # Additional Inputs button with lock icon on the right additional_inputs_btn = QPushButton("Additional Inputs") - - # Try to load the icon - if os.path.exists(lock_icon_path): - lock_icon = QIcon(lock_icon_path) - additional_inputs_btn.setIcon(lock_icon) - additional_inputs_btn.setIconSize(QSize(14, 14)) - additional_inputs_btn.setLayoutDirection(Qt.RightToLeft) # This puts icon on the right - + additional_inputs_btn.setCursor(Qt.CursorShape.PointingHandCursor) additional_inputs_btn.setStyleSheet(""" QPushButton { background-color: white; - color: #333; - font-weight: normal; - border: 1px solid #b0b0b0; - border-radius: 3px; - padding: 6px 16px; - min-height: 28px; - text-align: left; + color: black; + font-weight: bold; + font-size: 13px; + border-radius: 5px; + border: 1px solid black; + padding: 7px 20px; + text-align: center; } QPushButton:hover { - background-color: #f5f5f5; - border: 1px solid #909090; + background-color: #90AF13; + border: 1px solid #90AF13; + color: white; + } + QPushButton:pressed { + color: black; + background-color: white; + border: 1px solid black; } """) additional_inputs_btn.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) additional_inputs_btn.clicked.connect(self.show_additional_inputs) - top_bar.addWidget(additional_inputs_btn) + top_bar.addWidget(additional_inputs_btn) + + # Load the icon from resources + lock_button = QPushButton() + lock_button.setCursor(Qt.CursorShape.PointingHandCursor) + lock_state = [1] # Use list to make it mutable + lock_button.setIcon(QIcon(":/vectors/lock_close.svg")) + lock_button.setIconSize(QSize(30, 30)) + lock_button.setStyleSheet("border: none;") + top_bar.addWidget(lock_button) + + def toggle_lock(): + if lock_state[0] == 0: + lock_state[0] = 1 + lock_button.setIcon(QIcon(":/vectors/lock_close.svg")) + elif lock_state[0] == 1: + lock_state[0] = 0 + lock_button.setIcon(QIcon(":/vectors/lock_open.svg")) + + lock_button.clicked.connect(toggle_lock) panel_layout.addLayout(top_bar) @@ -675,25 +688,42 @@ def build_left_panel(self, field_list): scroll_area.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) scroll_area.setStyleSheet(""" QScrollArea { - border: none; - background-color: transparent; + background: transparent; + padding: 0px 5px; + border-top: 1px solid #909090; + border-bottom: 1px solid #909090; } - QScrollBar:vertical { + + QScrollArea QScrollBar:vertical { + border: none; background: #f0f0f0; - width: 10px; - margin: 0px; - border-radius: 0px; + width: 8px; + margin-left: 2px; } - QScrollBar::handle:vertical { + + QScrollArea QScrollBar::handle:vertical { background: #c0c0c0; - min-height: 30px; - border-radius: 0px; + border-radius: 4px; + min-height: 20px; } - QScrollBar::handle:vertical:hover { + + QScrollArea QScrollBar::handle:vertical:hover { background: #a0a0a0; } - QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical { - height: 0px; + + QScrollArea QScrollBar::handle:vertical:pressed { + background: #808080; + } + + QScrollArea QScrollBar::add-line:vertical, + QScrollArea QScrollBar::sub-line:vertical { + border: none; + background: none; + } + + QScrollArea QScrollBar::add-page:vertical, + QScrollArea QScrollBar::sub-page:vertical { + background: none; } """) @@ -702,73 +732,7 @@ def build_left_panel(self, field_list): group_container_layout = QVBoxLayout(group_container) group_container_layout.setContentsMargins(0, 0, 0, 0) group_container_layout.setSpacing(12) - - # === Superstructure Section (Contains Everything) === - structure_group = QGroupBox() - structure_group.setStyleSheet(""" - QGroupBox { - border: 2px solid #90AF13; - border-radius: 5px; - margin-top: 0px; - padding-top: 5px; - background-color: white; - } - """) - structure_layout = QVBoxLayout() - structure_layout.setContentsMargins(10, 10, 10, 10) - structure_layout.setSpacing(10) - - # Header with title and collapse icon - struct_header = QHBoxLayout() - struct_title = QLabel("Superstructure") - struct_title.setStyleSheet("font-size: 11px; font-weight: bold; color: #333;") - struct_header.addWidget(struct_title) - struct_header.addStretch() - - # Get the path to dropdown SVG - base_dir = os.path.dirname(os.path.abspath(__file__)) - svg_down = os.path.join(base_dir, "dropdown_down.svg").replace("\\", "/") - svg_up = os.path.join(base_dir, "dropdown_up.svg").replace("\\", "/") - - # Collapse/expand toggle using SVG icon - toggle_btn = QPushButton() - toggle_btn.setCheckable(True) - toggle_btn.setChecked(True) - toggle_btn.setIcon(QIcon(svg_up)) - toggle_btn.setIconSize(QSize(16, 16)) - toggle_btn.setStyleSheet(""" - QPushButton { - background: transparent; - border: none; - padding: 2px; - } - QPushButton:hover { - background: transparent; - } - QPushButton:pressed { - background: transparent; - } - """) - struct_header.addWidget(toggle_btn) - structure_layout.addLayout(struct_header) - - # body widget that contains everything inside the Superstructure and can be hidden - structure_body = QFrame() - structure_body.setFrameShape(QFrame.NoFrame) - structure_body_layout = QVBoxLayout(structure_body) - structure_body_layout.setContentsMargins(0, 0, 0, 0) - structure_body_layout.setSpacing(10) - structure_body.setVisible(True) - structure_layout.addWidget(structure_body) - - def _toggle_structure(checked): - # checked True means show body (open) - structure_body.setVisible(checked) - toggle_btn.setIcon(QIcon(svg_up if checked else svg_down)) - - toggle_btn.toggled.connect(_toggle_structure) - # === Type of Structure Box === type_box = QGroupBox("Type of Structure") type_box.setStyleSheet(""" @@ -787,6 +751,7 @@ def _toggle_structure(checked): subcontrol-position: top left; left: 8px; padding: 0 4px; + margin-top: 4px; background-color: white; color: #333; } @@ -798,7 +763,13 @@ def _toggle_structure(checked): # Type of Structure field type_row = QHBoxLayout() type_field_label = QLabel("Type of Structure") - type_field_label.setStyleSheet("font-size: 10px; color: #555; font-weight: normal;") + type_field_label.setStyleSheet(""" + QLabel { + color: #000000; + font-size: 12px; + background: transparent; + } + """) type_field_label.setMinimumWidth(110) self.structure_type_combo = NoScrollComboBox() @@ -811,12 +782,18 @@ def _toggle_structure(checked): type_box_layout.addLayout(type_row) self.structure_note = QLabel("*Other structures not included") - self.structure_note.setStyleSheet("font-size: 9px; color: #d32f2f; font-style: italic;") + self.structure_note.setStyleSheet(""" + QLabel { + color: #000000; + font-size: 12px; + background: transparent; + } + """) self.structure_note.setVisible(False) type_box_layout.addWidget(self.structure_note) self.structure_type_combo.currentTextChanged.connect(self.on_structure_type_changed) - structure_body_layout.addWidget(type_box) + group_container_layout.addWidget(type_box) # === Project Location Box === location_box = QGroupBox() @@ -834,12 +811,19 @@ def _toggle_structure(checked): location_box_layout.setSpacing(8) loc_header = QHBoxLayout() - loc_title = QLabel("Project Location:") - loc_title.setStyleSheet("font-size: 10px; font-weight: bold; color: #333;") + loc_title = QLabel("Project Location") + loc_title.setStyleSheet(""" + QLabel { + color: #000000; + font-size: 12px; + background: transparent; + } + """) + loc_title.setMinimumWidth(110) loc_header.addWidget(loc_title) - loc_header.addStretch() add_here_btn = QPushButton("Add Here") + add_here_btn.setCursor(Qt.CursorShape.PointingHandCursor) add_here_btn.setStyleSheet(""" QPushButton { background-color: #90AF13; @@ -856,7 +840,7 @@ def _toggle_structure(checked): } """) add_here_btn.clicked.connect(self.show_project_location_dialog) - loc_header.addWidget(add_here_btn) + loc_header.addWidget(add_here_btn, 1) location_box_layout.addLayout(loc_header) self.project_location_combo = NoScrollComboBox() @@ -865,7 +849,69 @@ def _toggle_structure(checked): self.project_location_combo.currentTextChanged.connect(self.on_project_location_changed) self.project_location_combo.hide() - structure_body_layout.addWidget(location_box) + group_container_layout.addWidget(location_box) + + # === Superstructure Section (Contains Everything) === + structure_group = QGroupBox() + structure_group.setStyleSheet(""" + QGroupBox { + border: 1px solid #90AF13; + border-radius: 5px; + margin-top: 0px; + padding-top: 5px; + background-color: white; + } + """) + structure_layout = QVBoxLayout() + structure_layout.setContentsMargins(10, 10, 10, 10) + structure_layout.setSpacing(10) + + # Header with title and collapse icon + struct_header = QHBoxLayout() + struct_title = QLabel("Superstructure") + struct_title.setStyleSheet("font-size: 13px; font-weight: bold; color: #333;") + struct_header.addWidget(struct_title) + struct_header.addStretch() + + # Collapse/expand toggle using SVG icon + toggle_btn = QPushButton() + toggle_btn.setCursor(Qt.CursorShape.PointingHandCursor) + toggle_btn.setCheckable(True) + toggle_btn.setChecked(True) + toggle_btn.setIcon(QIcon(":/vectors/arrow_up_light.svg")) + toggle_btn.setIconSize(QSize(20, 20)) + toggle_btn.setStyleSheet(""" + QPushButton { + background: transparent; + border: none; + padding: 2px; + } + QPushButton:hover { + background: transparent; + } + QPushButton:pressed { + background: transparent; + } + """) + struct_header.addWidget(toggle_btn) + + structure_layout.addLayout(struct_header) + + # body widget that contains everything inside the Superstructure and can be hidden + structure_body = QFrame() + structure_body.setFrameShape(QFrame.NoFrame) + structure_body_layout = QVBoxLayout(structure_body) + structure_body_layout.setContentsMargins(0, 0, 0, 0) + structure_body_layout.setSpacing(10) + structure_body.setVisible(True) + structure_layout.addWidget(structure_body) + + def _toggle_structure(checked): + # checked True means show body (open) + structure_body.setVisible(checked) + toggle_btn.setIcon(QIcon(":/vectors/arrow_up_light.svg" if checked else ":/vectors/arrow_down_light.svg")) + + toggle_btn.toggled.connect(_toggle_structure) # === Geometric Details Box === geo_box = QGroupBox("Geometric Details") @@ -885,6 +931,7 @@ def _toggle_structure(checked): subcontrol-position: top left; left: 8px; padding: 0 4px; + margin-top: 4px; background-color: white; color: #333; } @@ -895,8 +942,14 @@ def _toggle_structure(checked): # Span span_row = QHBoxLayout() - span_label = QLabel("Span (m):") - span_label.setStyleSheet("font-size: 10px; color: #555; font-weight: normal;") + span_label = QLabel("Span (m)") + span_label.setStyleSheet(""" + QLabel { + color: #000000; + font-size: 12px; + background: transparent; + } + """) span_label.setMinimumWidth(110) self.span_input = QLineEdit() self.span_input.setObjectName(KEY_SPAN) @@ -909,8 +962,14 @@ def _toggle_structure(checked): # Carriageway Width carriageway_row = QHBoxLayout() - carriageway_label = QLabel("Carriageway (m):") - carriageway_label.setStyleSheet("font-size: 10px; color: #555; font-weight: normal;") + carriageway_label = QLabel("Carriageway (m)") + carriageway_label.setStyleSheet(""" + QLabel { + color: #000000; + font-size: 12px; + background: transparent; + } + """) carriageway_label.setMinimumWidth(110) self.carriageway_input = QLineEdit() self.carriageway_input.setObjectName(KEY_CARRIAGEWAY_WIDTH) @@ -923,8 +982,14 @@ def _toggle_structure(checked): # Footpath footpath_row = QHBoxLayout() - footpath_label = QLabel("Footpath:") - footpath_label.setStyleSheet("font-size: 10px; color: #555; font-weight: normal;") + footpath_label = QLabel("Footpath") + footpath_label.setStyleSheet(""" + QLabel { + color: #000000; + font-size: 12px; + background: transparent; + } + """) footpath_label.setMinimumWidth(110) self.footpath_combo = NoScrollComboBox() self.footpath_combo.setObjectName(KEY_FOOTPATH) @@ -938,8 +1003,14 @@ def _toggle_structure(checked): # Skew Angle skew_row = QHBoxLayout() - skew_label = QLabel("Skew Angle:") - skew_label.setStyleSheet("font-size: 10px; color: #555; font-weight: normal;") + skew_label = QLabel("Skew Angle") + skew_label.setStyleSheet(""" + QLabel { + color: #000000; + font-size: 12px; + background: transparent; + } + """) skew_label.setMinimumWidth(110) self.skew_input = QLineEdit() self.skew_input.setObjectName(KEY_SKEW_ANGLE) @@ -953,12 +1024,19 @@ def _toggle_structure(checked): # Additional Geometry (inside Geometric Details) add_geo_row = QHBoxLayout() - add_geo_label = QLabel("Additional Geometry:") - add_geo_label.setStyleSheet("font-size: 10px; color: #555; font-weight: bold;") + add_geo_label = QLabel("Additional Geometry") + add_geo_label.setStyleSheet(""" + QLabel { + color: #000000; + font-size: 12px; + background: transparent; + } + """) add_geo_label.setMinimumWidth(110) add_geo_row.addWidget(add_geo_label) modify_geo_btn = QPushButton("Modify Here") + modify_geo_btn.setCursor(Qt.CursorShape.PointingHandCursor) modify_geo_btn.setStyleSheet(""" QPushButton { background-color: #90AF13; @@ -998,6 +1076,7 @@ def _toggle_structure(checked): subcontrol-position: top left; left: 8px; padding: 0 4px; + margin-top: 4px; background-color: white; color: #333; } @@ -1006,36 +1085,16 @@ def _toggle_structure(checked): material_box_layout.setContentsMargins(8, 8, 8, 8) material_box_layout.setSpacing(8) - # Material Properties header with button - mat_prop_header = QHBoxLayout() - mat_prop_title = QLabel("Material Properties:") - mat_prop_title.setStyleSheet("font-size: 10px; font-weight: bold; color: #333;") - mat_prop_header.addWidget(mat_prop_title) - mat_prop_header.addStretch() - - modify_mat_btn = QPushButton("Modify Here") - modify_mat_btn.setStyleSheet(""" - QPushButton { - background-color: #90AF13; - color: white; - font-weight: bold; - border: none; - border-radius: 4px; - padding: 8px 20px; - font-size: 11px; - min-width: 80px; - } - QPushButton:hover { - background-color: #7a9a12; - } - """) - mat_prop_header.addWidget(modify_mat_btn) - material_box_layout.addLayout(mat_prop_header) - # Girder girder_row = QHBoxLayout() - girder_label = QLabel("Girder:") - girder_label.setStyleSheet("font-size: 10px; color: #555; font-weight: normal;") + girder_label = QLabel("Girder") + girder_label.setStyleSheet(""" + QLabel { + color: #000000; + font-size: 12px; + background: transparent; + } + """) girder_label.setMinimumWidth(110) self.girder_combo = NoScrollComboBox() self.girder_combo.setObjectName(KEY_GIRDER) @@ -1047,8 +1106,14 @@ def _toggle_structure(checked): # Cross Bracing cross_bracing_row = QHBoxLayout() - cross_bracing_label = QLabel("Cross Bracing:") - cross_bracing_label.setStyleSheet("font-size: 10px; color: #555; font-weight: normal;") + cross_bracing_label = QLabel("Cross Bracing") + cross_bracing_label.setStyleSheet(""" + QLabel { + color: #000000; + font-size: 12px; + background: transparent; + } + """) cross_bracing_label.setMinimumWidth(110) self.cross_bracing_combo = NoScrollComboBox() self.cross_bracing_combo.setObjectName(KEY_CROSS_BRACING) @@ -1060,8 +1125,14 @@ def _toggle_structure(checked): # Deck deck_row = QHBoxLayout() - deck_label = QLabel("Deck:") - deck_label.setStyleSheet("font-size: 10px; color: #555; font-weight: normal;") + deck_label = QLabel("Deck") + deck_label.setStyleSheet(""" + QLabel { + color: #000000; + font-size: 12px; + background: transparent; + } + """) deck_label.setMinimumWidth(110) self.deck_combo = NoScrollComboBox() self.deck_combo.setObjectName(KEY_DECK_CONCRETE_GRADE_BASIC) @@ -1071,6 +1142,39 @@ def _toggle_structure(checked): deck_row.addWidget(deck_label) deck_row.addWidget(self.deck_combo, 1) material_box_layout.addLayout(deck_row) + + # Material Properties header with button + mat_prop_header = QHBoxLayout() + mat_prop_title = QLabel("Modify Properties") + mat_prop_title.setStyleSheet(""" + QLabel { + color: #000000; + font-size: 12px; + background: transparent; + } + """) + mat_prop_title.setMinimumWidth(110) + mat_prop_header.addWidget(mat_prop_title) + + modify_mat_btn = QPushButton("Modify Here") + modify_mat_btn.setCursor(Qt.CursorShape.PointingHandCursor) + modify_mat_btn.setStyleSheet(""" + QPushButton { + background-color: #90AF13; + color: white; + font-weight: bold; + border: none; + border-radius: 4px; + padding: 8px 20px; + font-size: 11px; + min-width: 80px; + } + QPushButton:hover { + background-color: #7a9a12; + } + """) + mat_prop_header.addWidget(modify_mat_btn, 1) + material_box_layout.addLayout(mat_prop_header) structure_body_layout.addWidget(material_box) @@ -1082,7 +1186,7 @@ def _toggle_structure(checked): sub_group = QGroupBox() sub_group.setStyleSheet(""" QGroupBox { - border: 2px solid #90AF13; + border: 1px solid #90AF13; border-radius: 5px; margin-top: 8px; padding-top: 5px; @@ -1096,15 +1200,16 @@ def _toggle_structure(checked): # Header with toggle sub_header = QHBoxLayout() sub_title = QLabel("Substructure") - sub_title.setStyleSheet("font-size: 11px; font-weight: bold; color: #333;") + sub_title.setStyleSheet("font-size: 13px; font-weight: bold; color: #333;") sub_header.addWidget(sub_title) sub_header.addStretch() sub_toggle = QPushButton() + sub_toggle.setCursor(Qt.CursorShape.PointingHandCursor) sub_toggle.setCheckable(True) sub_toggle.setChecked(True) - sub_toggle.setIcon(QIcon(svg_up)) - sub_toggle.setIconSize(QSize(16, 16)) + sub_toggle.setIcon(QIcon(":/vectors/arrow_up_light.svg")) + sub_toggle.setIconSize(QSize(20, 20)) sub_toggle.setStyleSheet(""" QPushButton { background: transparent; @@ -1131,7 +1236,7 @@ def _toggle_structure(checked): def _toggle_sub(checked): sub_body.setVisible(checked) - sub_toggle.setIcon(QIcon(svg_up if checked else svg_down)) + sub_toggle.setIcon(QIcon(":/vectors/arrow_up_light.svg" if checked else ":/vectors/arrow_down_light.svg")) sub_toggle.toggled.connect(_toggle_sub) @@ -1149,13 +1254,11 @@ def _toggle_sub(checked): btn_button_layout.setContentsMargins(0, 15, 0, 0) btn_button_layout.setSpacing(10) - save_input_btn = QPushButton("Save Input") - save_input_btn.setStyleSheet(style_main_buttons()) + save_input_btn = DockCustomButton("Save Input", ":/vectors/save.svg") save_input_btn.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) btn_button_layout.addWidget(save_input_btn) - design_btn = QPushButton("Design") - design_btn.setStyleSheet(style_main_buttons()) + design_btn = DockCustomButton("Design", ":/vectors/design.svg") design_btn.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) btn_button_layout.addWidget(design_btn) @@ -1168,18 +1271,28 @@ def _toggle_sub(checked): h_scroll_area.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) h_scroll_area.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) h_scroll_area.setStyleSheet(""" - QScrollArea { - border: none; - background-color: transparent; + QScrollArea{ + background: transparent; } - QScrollBar:horizontal { - background: #f0f0f0; - height: 10px; - margin: 0px; + QScrollBar:horizontal{ + background: #E0E0E0; + height: 8px; + margin: 3px 0px 0px 0px; + border-radius: 2px; } - QScrollBar::handle:horizontal { - background: #c0c0c0; + QScrollBar::handle:horizontal{ + background: #A0A0A0; min-width: 30px; + border-radius: 2px; + } + QScrollBar::handle:horizontal:hover{ + background: #707070; + } + QScrollBar::add-line:horizontal, QScrollBar::sub-line:horizontal{ + width: 0px; + } + QScrollBar::add-page:horizontal, QScrollBar::sub-page:horizontal{ + background: none; } """) h_scroll_area.setWidget(self.left_panel) diff --git a/src/osbridge/ui/output_dock.py b/src/osbridge/ui/output_dock.py new file mode 100644 index 00000000..ca3e16aa --- /dev/null +++ b/src/osbridge/ui/output_dock.py @@ -0,0 +1,443 @@ +from PySide6.QtWidgets import ( + QWidget, QVBoxLayout, QHBoxLayout, QLabel, QSizePolicy, + QPushButton, QGroupBox, QCheckBox, QScrollArea, QFrame, +) +from PySide6.QtCore import Qt, QSize +from PySide6.QtGui import QIcon + +from osbridge.ui.custom_buttons import DockCustomButton +from osbridge.ui.input_dock import NoScrollComboBox, apply_field_style + + +class OutputDock(QWidget): + """Output dock with collapsible design controls and scrollable layout.""" + + def __init__(self): + super().__init__() + self.setStyleSheet("background: transparent;") + self.init_ui() + + def init_ui(self): + # Main horizontal layout to hold toggle strip and content + self.main_layout = QHBoxLayout(self) + self.main_layout.setContentsMargins(0, 0, 0, 0) + self.main_layout.setSpacing(0) + + # Toggle strip on the left + self.toggle_strip = QWidget() + self.toggle_strip.setStyleSheet("background-color: #90AF13;") + self.toggle_strip.setFixedWidth(6) + toggle_layout = QVBoxLayout(self.toggle_strip) + toggle_layout.setContentsMargins(0, 0, 0, 0) + toggle_layout.setSpacing(0) + toggle_layout.setAlignment(Qt.AlignVCenter | Qt.AlignLeft) + + self.toggle_btn = QPushButton("❯") + self.toggle_btn.setCursor(Qt.CursorShape.PointingHandCursor) + self.toggle_btn.setFixedSize(6, 60) + self.toggle_btn.setToolTip("Hide panel") + self.toggle_btn.setStyleSheet(""" + QPushButton { + background-color: #7a9a12; + color: white; + font-size: 12px; + font-weight: bold; + padding: 0px; + border: none; + } + QPushButton:hover { + background-color: #6a8a10; + } + """) + toggle_layout.addStretch() + toggle_layout.addWidget(self.toggle_btn) + toggle_layout.addStretch() + self.main_layout.addWidget(self.toggle_strip) + + # Content container + content_container = QWidget() + content_container.setStyleSheet("background-color: white;") + content_layout = QVBoxLayout(content_container) + content_layout.setContentsMargins(8, 8, 8, 8) + content_layout.setSpacing(10) + + # Top Bar with buttons + top_bar = QHBoxLayout() + top_bar.setSpacing(8) + top_bar.setContentsMargins(0, 0, 0, 15) + + input_dock_btn = QPushButton("Output Dock") + input_dock_btn.setStyleSheet(""" + QPushButton { + background-color: #90AF13; + color: white; + font-weight: bold; + font-size: 13px; + border: none; + border-radius: 4px; + padding: 7px 20px; + min-width: 80px; + } + """) + input_dock_btn.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) + top_bar.addWidget(input_dock_btn) + top_bar.addStretch() + content_layout.addLayout(top_bar) + + scroll = QScrollArea() + scroll.setWidgetResizable(True) + scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + scroll.setStyleSheet("QScrollArea { border: none; background: white; }") + + scroll_content = QWidget() + scroll_layout = QVBoxLayout(scroll_content) + scroll_layout.setContentsMargins(0, 0, 0, 0) + scroll_layout.setSpacing(10) + + results_group = QGroupBox("Analysis Results") + results_group.setStyleSheet( + """ + QGroupBox { + font-weight: bold; + font-size: 11px; + color: #333; + border: 1px solid #90AF13; + border-radius: 4px; + margin-top: 8px; + padding-top: 12px; + background-color: white; + } + QGroupBox::title { + subcontrol-origin: margin; + subcontrol-position: top left; + left: 8px; + padding: 0 4px; + background-color: white; + } + """ + ) + results_layout = QVBoxLayout(results_group) + results_layout.setContentsMargins(10, 8, 10, 10) + results_layout.setSpacing(8) + + member_row = QHBoxLayout() + member_label = QLabel("Member:") + member_label.setStyleSheet("font-size: 10px; color: #333; font-weight: normal;") + member_label.setMinimumWidth(100) + self.member_combo = NoScrollComboBox() + self.member_combo.addItems(["All"]) + apply_field_style(self.member_combo) + member_row.addWidget(member_label) + member_row.addWidget(self.member_combo) + results_layout.addLayout(member_row) + + load_combo_row = QHBoxLayout() + load_combo_label = QLabel("Load Combination:") + load_combo_label.setStyleSheet("font-size: 10px; color: #333; font-weight: normal;") + load_combo_label.setMinimumWidth(100) + self.load_combo = NoScrollComboBox() + self.load_combo.addItems(["Envelope"]) + apply_field_style(self.load_combo) + load_combo_row.addWidget(load_combo_label) + load_combo_row.addWidget(self.load_combo) + results_layout.addLayout(load_combo_row) + + forces_grid = QHBoxLayout() + forces_grid.setSpacing(8) + + col1 = QVBoxLayout() + for text in ["Fx", "Mx", "Dx"]: + cb = QCheckBox(text) + cb.setStyleSheet("font-size: 10px; color: #333;") + col1.addWidget(cb) + col2 = QVBoxLayout() + for text in ["Fy", "My", "Dy"]: + cb = QCheckBox(text) + cb.setStyleSheet("font-size: 10px; color: #333;") + col2.addWidget(cb) + col3 = QVBoxLayout() + for text in ["Fz", "Mz", "Dz"]: + cb = QCheckBox(text) + cb.setStyleSheet("font-size: 10px; color: #333;") + col3.addWidget(cb) + forces_grid.addLayout(col1) + forces_grid.addLayout(col2) + forces_grid.addLayout(col3) + results_layout.addLayout(forces_grid) + + display_label = QLabel("Display Options:") + display_label.setStyleSheet("font-size: 10px; color: #333; font-weight: normal; margin-top: 4px;") + results_layout.addWidget(display_label) + + display_row = QHBoxLayout() + display_row.setSpacing(12) + for text in ["Max", "Min"]: + cb = QCheckBox(text) + cb.setStyleSheet("font-size: 10px; color: #333;") + display_row.addWidget(cb) + display_row.addStretch() + results_layout.addLayout(display_row) + + utilization_check = QCheckBox("Controlling Utilization Ratio") + utilization_check.setStyleSheet("font-size: 10px; color: #333;") + results_layout.addWidget(utilization_check) + + scroll_layout.addWidget(results_group) + + design_group = QGroupBox("Design") + design_group.setStyleSheet( + """ + QGroupBox { + font-weight: bold; + font-size: 11px; + color: #333; + border: 1px solid #90AF13; + border-radius: 4px; + margin-top: 8px; + padding-top: 12px; + background-color: white; + } + QGroupBox::title { + subcontrol-origin: margin; + subcontrol-position: top left; + left: 8px; + padding: 0 4px; + background-color: white; + } + """ + ) + design_layout = QVBoxLayout(design_group) + design_layout.setContentsMargins(10, 8, 10, 10) + design_layout.setSpacing(8) + + # === Superstructure Section (Contains Everything) === + structure_group = QGroupBox() + structure_group.setStyleSheet(""" + QGroupBox { + border: 1px solid #90AF13; + border-radius: 5px; + margin-top: 0px; + padding-top: 5px; + background-color: white; + } + """) + structure_layout = QVBoxLayout() + structure_layout.setContentsMargins(10, 10, 10, 10) + structure_layout.setSpacing(10) + + # Header with title and collapse icon + struct_header = QHBoxLayout() + struct_title = QLabel("Superstructure") + struct_title.setStyleSheet("font-size: 13px; font-weight: bold; color: #333;") + struct_header.addWidget(struct_title) + struct_header.addStretch() + + # Collapse/expand toggle using SVG icon + toggle_btn = QPushButton() + toggle_btn.setCursor(Qt.CursorShape.PointingHandCursor) + toggle_btn.setCheckable(True) + toggle_btn.setChecked(True) + toggle_btn.setIcon(QIcon(":/vectors/arrow_up_light.svg")) + toggle_btn.setIconSize(QSize(20, 20)) + toggle_btn.setStyleSheet(""" + QPushButton { + background: transparent; + border: none; + padding: 2px; + } + QPushButton:hover { + background: transparent; + } + QPushButton:pressed { + background: transparent; + } + """) + struct_header.addWidget(toggle_btn) + + structure_layout.addLayout(struct_header) + + # body widget that contains everything inside the Superstructure and can be hidden + structure_body = QFrame() + structure_body.setFrameShape(QFrame.NoFrame) + structure_body_layout = QVBoxLayout(structure_body) + structure_body_layout.setContentsMargins(0, 0, 0, 0) + structure_body_layout.setSpacing(10) + structure_body.setVisible(True) + + # Additional Geometry (inside Superstructure body) + add_geo_row = QHBoxLayout() + add_geo_label = QLabel("Steel Design") + add_geo_label.setStyleSheet(""" + QLabel { + color: #000000; + font-size: 12px; + background: transparent; + } + """) + add_geo_label.setMinimumWidth(110) + add_geo_row.addWidget(add_geo_label) + + modify_geo_btn = QPushButton("Here") + modify_geo_btn.setCursor(Qt.CursorShape.PointingHandCursor) + modify_geo_btn.setStyleSheet(""" + QPushButton { + background-color: #90AF13; + color: white; + font-weight: bold; + border: none; + border-radius: 4px; + padding: 8px 20px; + font-size: 11px; + min-width: 80px; + } + QPushButton:hover { + background-color: #7a9a12; + } + """) + modify_geo_btn.clicked.connect(self.show_additional_inputs) + add_geo_row.addWidget(modify_geo_btn, 1) + structure_body_layout.addLayout(add_geo_row) + + #--------------------------------------------- + + # Additional Geometry (inside Superstructure body) + add_geo_row = QHBoxLayout() + add_geo_label = QLabel("Deck Design") + add_geo_label.setStyleSheet(""" + QLabel { + color: #000000; + font-size: 12px; + background: transparent; + } + """) + add_geo_label.setMinimumWidth(110) + add_geo_row.addWidget(add_geo_label) + + modify_geo_btn = QPushButton("Here") + modify_geo_btn.setCursor(Qt.CursorShape.PointingHandCursor) + modify_geo_btn.setStyleSheet(""" + QPushButton { + background-color: #90AF13; + color: white; + font-weight: bold; + border: none; + border-radius: 4px; + padding: 8px 20px; + font-size: 11px; + min-width: 80px; + } + QPushButton:hover { + background-color: #7a9a12; + } + """) + modify_geo_btn.clicked.connect(self.show_additional_inputs) + add_geo_row.addWidget(modify_geo_btn, 1) + structure_body_layout.addLayout(add_geo_row) + + + # Add body to structure layout + structure_layout.addWidget(structure_body) + + def _toggle_structure(checked): + # checked True means show body (open) + structure_body.setVisible(checked) + toggle_btn.setIcon(QIcon(":/vectors/arrow_up_light.svg" if checked else ":/vectors/arrow_down_light.svg")) + + toggle_btn.toggled.connect(_toggle_structure) + structure_group.setLayout(structure_layout) + design_layout.addWidget(structure_group) + + # === Substructure Section (Contains Everything) === + structure_group = QGroupBox() + structure_group.setStyleSheet(""" + QGroupBox { + border: 1px solid #90AF13; + border-radius: 5px; + margin-top: 0px; + padding-top: 5px; + background-color: white; + } + """) + structure_layout = QVBoxLayout() + structure_layout.setContentsMargins(10, 10, 10, 10) + structure_layout.setSpacing(10) + + # Header with title and collapse icon + struct_header = QHBoxLayout() + struct_title = QLabel("Substructure") + struct_title.setStyleSheet("font-size: 13px; font-weight: bold; color: #333;") + struct_header.addWidget(struct_title) + struct_header.addStretch() + + # Collapse/expand toggle using SVG icon + toggle_btn = QPushButton() + toggle_btn.setCursor(Qt.CursorShape.PointingHandCursor) + toggle_btn.setCheckable(True) + toggle_btn.setChecked(True) + toggle_btn.setIcon(QIcon(":/vectors/arrow_up_light.svg")) + toggle_btn.setIconSize(QSize(20, 20)) + toggle_btn.setStyleSheet(""" + QPushButton { + background: transparent; + border: none; + padding: 2px; + } + QPushButton:hover { + background: transparent; + } + QPushButton:pressed { + background: transparent; + } + """) + struct_header.addWidget(toggle_btn) + + structure_layout.addLayout(struct_header) + + # body widget that contains everything inside the Superstructure and can be hidden + structure_body = QFrame() + structure_body.setFrameShape(QFrame.NoFrame) + structure_body_layout = QVBoxLayout(structure_body) + structure_body_layout.setContentsMargins(0, 0, 0, 0) + structure_body_layout.setSpacing(10) + structure_body.setVisible(True) + + + # Add body to structure layout + structure_layout.addWidget(structure_body) + + def _toggle_structure(checked): + # checked True means show body (open) + structure_body.setVisible(checked) + toggle_btn.setIcon(QIcon(":/vectors/arrow_up_light.svg" if checked else ":/vectors/arrow_down_light.svg")) + + toggle_btn.toggled.connect(_toggle_structure) + structure_group.setLayout(structure_layout) + design_layout.addWidget(structure_group) + + scroll_layout.addWidget(design_group) + scroll_layout.addStretch() + + scroll.setWidget(scroll_content) + content_layout.addWidget(scroll) + + h_layout = QHBoxLayout() + h_layout.setSpacing(5) + h_layout.setContentsMargins(0, 0, 0, 0) + + results_btn = DockCustomButton("Generate Results Table", ":/vectors/design_report.svg") + results_btn.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) + h_layout.addWidget(results_btn) + + report_btn = DockCustomButton("Generate Report", ":/vectors/design_report.svg") + report_btn.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) + + h_layout.addWidget(report_btn) + content_layout.addLayout(h_layout) + + # Add content container to main layout + self.main_layout.addWidget(content_container) + + def show_additional_inputs(self): + """Handle showing additional geometry inputs.""" + # Implement your logic here + print("Show additional inputs clicked") \ No newline at end of file From 5f40c8e6f19860122920010272410f0e80bbf786 Mon Sep 17 00:00:00 2001 From: Garvit S Rathore Date: Sat, 29 Nov 2025 17:22:12 +0530 Subject: [PATCH 36/59] Update member properties first sub type --- .../additional_inputs.cpython-311.pyc | Bin 97211 -> 106296 bytes src/osbridge/additional_inputs.py | 418 ++++++++++++++---- 2 files changed, 325 insertions(+), 93 deletions(-) diff --git a/src/osbridge/__pycache__/additional_inputs.cpython-311.pyc b/src/osbridge/__pycache__/additional_inputs.cpython-311.pyc index 4ba3ef5587c930ebfe67952053899be9a86b75ef..60f02e5d7abcd58eb8a4cdfb5da88349afcbb5cb 100644 GIT binary patch delta 12773 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 UySv6bEte;^uac0uN8zJ?0n&ua#Q*>R diff --git a/src/osbridge/additional_inputs.py b/src/osbridge/additional_inputs.py index b39dc0c9..b8646ea7 100644 --- a/src/osbridge/additional_inputs.py +++ b/src/osbridge/additional_inputs.py @@ -44,8 +44,9 @@ def get_combobox_style(): subcontrol-position: center right; width: 26px; height: 26px; - border: 1px solid #606060; - border-radius: 13px; + width: 26px; + height: 26px; + border: none; background: transparent; right: 8px; }} @@ -1122,7 +1123,6 @@ class GirderDetailsTab(QWidget): def __init__(self, parent=None): super().__init__(parent) - self.plate_rows = [] self.init_ui() def init_ui(self): @@ -1145,116 +1145,348 @@ def init_ui(self): scroll.setWidget(container) container_layout = QVBoxLayout(container) - container_layout.setContentsMargins(0, 0, 0, 0) - container_layout.setSpacing(0) + container_layout.setContentsMargins(10, 10, 10, 10) + container_layout.setSpacing(10) - form_frame = QFrame() - form_frame.setStyleSheet("QFrame { background: transparent; }") - self.form_layout = QGridLayout(form_frame) - self.form_layout.setContentsMargins(0, 0, 0, 0) - self.form_layout.setHorizontalSpacing(28) - self.form_layout.setVerticalSpacing(20) - self.form_layout.setColumnMinimumWidth(0, 220) - self.form_layout.setColumnStretch(1, 1) - container_layout.addWidget(form_frame) - container_layout.addStretch() + # Common label style + label_style = "QLabel { color: #333333; font-size: 11px; background-color: transparent; }" + title_style = "QLabel { color: #333333; font-weight: bold; font-size: 12px; margin-bottom: 10px; background-color: transparent; }" + + # --- Top Section --- + top_group = QGroupBox() + top_group.setStyleSheet(""" + QGroupBox { + background-color: white; + border: 1px solid #b0b0b0; + border-radius: 8px; + } + """) + top_layout = QGridLayout(top_group) + top_layout.setContentsMargins(15, 15, 15, 15) + top_layout.setHorizontalSpacing(20) + top_layout.setVerticalSpacing(15) + + # Row 0 + lbl_girder = QLabel("Select Girder:") + lbl_girder.setStyleSheet(label_style) + top_layout.addWidget(lbl_girder, 0, 0) + + self.select_girder = QComboBox() + self.select_girder.addItems(["Girder 1", "Girder 2", "Girder 3"]) # Placeholder items + apply_field_style(self.select_girder) + top_layout.addWidget(self.select_girder, 0, 1) + + # Spacer + top_layout.addItem(QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum), 0, 2) + + # Row 1 + lbl_span = QLabel("Span:") + lbl_span.setStyleSheet(label_style) + top_layout.addWidget(lbl_span, 1, 0) + + self.span_combo = QComboBox() + self.span_combo.addItems(["Custom", "Span 1", "Span 2"]) + apply_field_style(self.span_combo) + top_layout.addWidget(self.span_combo, 1, 1) + + lbl_member_id = QLabel("Member ID:") + lbl_member_id.setStyleSheet(label_style) + top_layout.addWidget(lbl_member_id, 1, 3) + + self.member_id = QLineEdit("G1-1") + apply_field_style(self.member_id) + top_layout.addWidget(self.member_id, 1, 4) + + # Row 2 + lbl_dist = QLabel("Distance from left edge (m):") + lbl_dist.setStyleSheet(label_style) + top_layout.addWidget(lbl_dist, 2, 0) + + dist_layout = QHBoxLayout() + dist_layout.setSpacing(10) + + dist_start_layout = QVBoxLayout() + self.dist_start = QLineEdit() + apply_field_style(self.dist_start) + self.dist_start.setFixedWidth(80) + dist_start_layout.addWidget(self.dist_start) + + dist_start_label = QLabel("Start") + dist_start_label.setAlignment(Qt.AlignCenter) + dist_start_label.setStyleSheet("font-size: 10px; color: #555555;") + dist_start_layout.addWidget(dist_start_label) + dist_layout.addLayout(dist_start_layout) + + dist_end_layout = QVBoxLayout() + self.dist_end = QLineEdit() + apply_field_style(self.dist_end) + self.dist_end.setFixedWidth(80) + dist_end_layout.addWidget(self.dist_end) + + dist_end_label = QLabel("End") + dist_end_label.setAlignment(Qt.AlignCenter) + dist_end_label.setStyleSheet("font-size: 10px; color: #555555;") + dist_end_layout.addWidget(dist_end_label) + dist_layout.addLayout(dist_end_layout) + + top_layout.addLayout(dist_layout, 2, 1) + + lbl_length = QLabel("Length (m):") + lbl_length.setStyleSheet(label_style) + top_layout.addWidget(lbl_length, 2, 3) + + self.length_input = QLineEdit() + apply_field_style(self.length_input) + top_layout.addWidget(self.length_input, 2, 4) + + container_layout.addWidget(top_group) + + # --- Main Content (Left/Right) --- + content_layout = QHBoxLayout() + content_layout.setSpacing(10) + + # Left Column + left_column = QVBoxLayout() + left_column.setSpacing(10) + + # Section Inputs Group + inputs_group = QGroupBox() + inputs_group.setStyleSheet(""" + QGroupBox { + background-color: white; + border: 1px solid #b0b0b0; + border-radius: 8px; + padding-top: 10px; + } + """) + inputs_layout = QVBoxLayout(inputs_group) + inputs_layout.setContentsMargins(15, 15, 15, 15) + + inputs_title = QLabel("Section Inputs:") + inputs_title.setStyleSheet(title_style) + inputs_layout.addWidget(inputs_title) + + inputs_grid = QGridLayout() + inputs_grid.setHorizontalSpacing(15) + inputs_grid.setVerticalSpacing(15) + inputs_grid.setColumnStretch(1, 1) row = 0 - self.girder_type_label = self.create_label("Girder Type:") - self.girder_type_combo = QComboBox() - self.girder_type_combo.addItems(VALUES_GIRDER_TYPE) - apply_field_style(self.girder_type_combo) - self.form_layout.addWidget(self.girder_type_label, row, 0, Qt.AlignVCenter) - self.form_layout.addWidget(self.girder_type_combo, row, 1) + lbl_design = QLabel("Design:") + lbl_design.setStyleSheet(label_style) + inputs_grid.addWidget(lbl_design, row, 0) + + self.design_combo = QComboBox() + self.design_combo.addItems(["Customized", "Standard"]) + apply_field_style(self.design_combo) + inputs_grid.addWidget(self.design_combo, row, 1) row += 1 - self.is_section_label = self.create_label("Select IS Beam Section:") - self.is_beam_combo = QComboBox() - self.is_beam_combo.addItems([ - "Select Section", - "ISMB 100", "ISMB 125", "ISMB 150", "ISMB 175", "ISMB 200", - "ISMB 225", "ISMB 250", "ISMB 300", "ISMB 350", "ISMB 400", - "ISMB 450", "ISMB 500", "ISMB 550", "ISMB 600", - "ISWB 150", "ISWB 175", "ISWB 200", "ISWB 225", "ISWB 250", - "ISWB 300", "ISWB 350", "ISWB 400", "ISWB 450", "ISWB 500", "ISWB 550", "ISWB 600" - ]) - apply_field_style(self.is_beam_combo) - self.form_layout.addWidget(self.is_section_label, row, 0, Qt.AlignVCenter) - self.form_layout.addWidget(self.is_beam_combo, row, 1) + lbl_type = QLabel("Type:") + lbl_type.setStyleSheet(label_style) + inputs_grid.addWidget(lbl_type, row, 0) + + self.type_combo = QComboBox() + self.type_combo.addItems(["Welded", "Rolled"]) + apply_field_style(self.type_combo) + inputs_grid.addWidget(self.type_combo, row, 1) row += 1 + lbl_symmetry = QLabel("Symmetry:") + lbl_symmetry.setStyleSheet(label_style) + inputs_grid.addWidget(lbl_symmetry, row, 0) + self.symmetry_combo = QComboBox() - self.symmetry_combo.addItems(VALUES_GIRDER_SYMMETRY) + self.symmetry_combo.addItems(["Girder Symmetric", "Asymmetric"]) apply_field_style(self.symmetry_combo) - row = self.add_plate_row(row, "Girder Symmetry", self.symmetry_combo) - - self.top_width_field = OptimizableField("Top Flange Width") - self.prepare_optimizable_field(self.top_width_field) - row = self.add_plate_row(row, "Top Flange Width (mm):", self.top_width_field) - - self.top_thick_field = OptimizableField("Top Flange Thickness") - self.prepare_optimizable_field(self.top_thick_field) - row = self.add_plate_row(row, "Top Flange Thickness (mm):", self.top_thick_field) - - self.bottom_width_field = OptimizableField("Bottom Flange Width") - self.prepare_optimizable_field(self.bottom_width_field) - row = self.add_plate_row(row, "Bottom Flange Width (mm):", self.bottom_width_field) + inputs_grid.addWidget(self.symmetry_combo, row, 1) + row += 1 - self.bottom_thick_field = OptimizableField("Bottom Flange Thickness") - self.prepare_optimizable_field(self.bottom_thick_field) - row = self.add_plate_row(row, "Bottom Flange Thickness (mm):", self.bottom_thick_field) + lbl_depth = QLabel("Total Depth (mm):") + lbl_depth.setStyleSheet(label_style) + inputs_grid.addWidget(lbl_depth, row, 0) + + self.total_depth = QLineEdit() + apply_field_style(self.total_depth) + inputs_grid.addWidget(self.total_depth, row, 1) + row += 1 - self.depth_field = OptimizableField("Depth of Section") - self.prepare_optimizable_field(self.depth_field) - row = self.add_plate_row(row, "Depth of Section (mm):", self.depth_field) + lbl_web_thick = QLabel("Web Thickness (mm):") + lbl_web_thick.setStyleSheet(label_style) + inputs_grid.addWidget(lbl_web_thick, row, 0) + + self.web_thickness = QComboBox() + self.web_thickness.addItems(["All", "Custom"]) + apply_field_style(self.web_thickness) + inputs_grid.addWidget(self.web_thickness, row, 1) + row += 1 - self.web_thick_field = OptimizableField("Web Thickness") - self.prepare_optimizable_field(self.web_thick_field) - row = self.add_plate_row(row, "Web Thickness (mm):", self.web_thick_field) + lbl_top_width = QLabel("Width of Top Flange (mm):") + lbl_top_width.setStyleSheet(label_style) + inputs_grid.addWidget(lbl_top_width, row, 0) + + self.top_flange_width = QLineEdit() + apply_field_style(self.top_flange_width) + inputs_grid.addWidget(self.top_flange_width, row, 1) + row += 1 - self.torsion_combo = QComboBox() - self.torsion_combo.addItems(VALUES_TORSIONAL_RESTRAINT) - apply_field_style(self.torsion_combo) - row = self.add_plate_row(row, "Torsional Restraint:", self.torsion_combo) + lbl_top_thick = QLabel("Top Flange Thickness (mm):") + lbl_top_thick.setStyleSheet(label_style) + inputs_grid.addWidget(lbl_top_thick, row, 0) + + self.top_flange_thickness = QComboBox() + self.top_flange_thickness.addItems(["All", "Custom"]) + apply_field_style(self.top_flange_thickness) + inputs_grid.addWidget(self.top_flange_thickness, row, 1) + row += 1 - self.warp_combo = QComboBox() - self.warp_combo.addItems(VALUES_WARPING_RESTRAINT) - apply_field_style(self.warp_combo) - row = self.add_plate_row(row, "Warping Restraint:", self.warp_combo) + lbl_bot_width = QLabel("Width of Bottom Flange (mm):") + lbl_bot_width.setStyleSheet(label_style) + inputs_grid.addWidget(lbl_bot_width, row, 0) + + self.bottom_flange_width = QLineEdit() + apply_field_style(self.bottom_flange_width) + inputs_grid.addWidget(self.bottom_flange_width, row, 1) + row += 1 - self.web_type_combo = QComboBox() - self.web_type_combo.addItems(VALUES_WEB_TYPE) - apply_field_style(self.web_type_combo) - row = self.add_plate_row(row, "Web Type* :", self.web_type_combo) + lbl_bot_thick = QLabel("Bottom Flange Thickness (mm):") + lbl_bot_thick.setStyleSheet(label_style) + inputs_grid.addWidget(lbl_bot_thick, row, 0) + + self.bottom_flange_thickness = QComboBox() + self.bottom_flange_thickness.addItems(["All", "Custom"]) + apply_field_style(self.bottom_flange_thickness) + inputs_grid.addWidget(self.bottom_flange_thickness, row, 1) + row += 1 - self.girder_type_combo.currentTextChanged.connect(self.on_girder_type_changed) - self.on_girder_type_changed(self.girder_type_combo.currentText()) + inputs_layout.addLayout(inputs_grid) + left_column.addWidget(inputs_group) - def create_label(self, text): - label = QLabel(text) - label.setStyleSheet("font-size: 13px; color: #2f2f2f; font-weight: 600;") - return label + # Restraints Group + restraints_group = QGroupBox() + restraints_group.setStyleSheet(""" + QGroupBox { + background-color: white; + border: 1px solid #b0b0b0; + border-radius: 8px; + } + """) + restraints_layout = QGridLayout(restraints_group) + restraints_layout.setContentsMargins(15, 15, 15, 15) + restraints_layout.setHorizontalSpacing(15) + restraints_layout.setVerticalSpacing(15) + restraints_layout.setColumnStretch(1, 1) + + r_row = 0 + lbl_torsion = QLabel("Torsional Restraint:") + lbl_torsion.setStyleSheet(label_style) + restraints_layout.addWidget(lbl_torsion, r_row, 0) + + self.torsional_restraint = QComboBox() + self.torsional_restraint.addItems(["Fully Restrained", "Free"]) + apply_field_style(self.torsional_restraint) + restraints_layout.addWidget(self.torsional_restraint, r_row, 1) + r_row += 1 + + lbl_warping = QLabel("Warping Restraint:") + lbl_warping.setStyleSheet(label_style) + restraints_layout.addWidget(lbl_warping, r_row, 0) + + self.warping_restraint = QComboBox() + self.warping_restraint.addItems(["Both flanges fully restrained", "Free"]) + apply_field_style(self.warping_restraint) + restraints_layout.addWidget(self.warping_restraint, r_row, 1) + r_row += 1 + + lbl_web_type = QLabel("Web Type*:") + lbl_web_type.setStyleSheet(label_style) + restraints_layout.addWidget(lbl_web_type, r_row, 0) + + self.web_type = QComboBox() + self.web_type.addItems(["Thin Web with ITS", "Stocky Web"]) + apply_field_style(self.web_type) + restraints_layout.addWidget(self.web_type, r_row, 1) + + left_column.addWidget(restraints_group) + content_layout.addLayout(left_column, 1) + + # Right Column + right_column = QVBoxLayout() + right_column.setSpacing(10) + + # Image Preview + image_frame = QFrame() + image_frame.setStyleSheet(""" + QFrame { + background-color: #d9d9d9; + border: 1px solid #b0b0b0; + border-radius: 8px; + } + """) + image_frame.setMinimumHeight(200) + image_layout = QVBoxLayout(image_frame) + image_label = QLabel("Dynamic Image") + image_label.setStyleSheet("color: #333333; font-weight: bold;") + image_label.setAlignment(Qt.AlignCenter) + image_layout.addWidget(image_label) + + right_column.addWidget(image_frame) + + # Section Properties Group + props_group = QGroupBox() + props_group.setStyleSheet(""" + QGroupBox { + background-color: white; + border: 1px solid #b0b0b0; + border-radius: 8px; + padding-top: 10px; + } + """) + props_layout = QVBoxLayout(props_group) + props_layout.setContentsMargins(15, 15, 15, 15) + + props_title = QLabel("Section Properties:") + props_title.setStyleSheet(title_style) + props_layout.addWidget(props_title) + + props_grid = QGridLayout() + props_grid.setHorizontalSpacing(15) + props_grid.setVerticalSpacing(10) + props_grid.setColumnStretch(1, 1) + + properties = [ + ("Mass, M (Kg/m)", ""), + ("Sectional Area, a (cm2)", ""), + ("2nd Moment of Area, Iz (cm4)", ""), + ("2nd Moment of Area, Iv (cm4)", ""), + ("Radius of Gyration, rz (cm)", ""), + ("Radius of Gyration, rv (cm)", ""), + ("Elastic Modulus, Zz (cm3)", ""), + ("Elastic Modulus, Zv (cm3)", ""), + ("Plastic Modulus, Zuz (cm3)", ""), + ("Plastic Modulus, Zuv (cm3)", ""), + ("Torsion Constant, It (cm4)", ""), + ("Warping Constant, Iw (cm6)", ""), + ] - def prepare_optimizable_field(self, field): - field.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) - apply_field_style(field.mode_combo) - apply_field_style(field.input_field) + self.prop_inputs = {} + for i, (label_text, _) in enumerate(properties): + lbl_prop = QLabel(label_text) + lbl_prop.setStyleSheet(label_style) + props_grid.addWidget(lbl_prop, i, 0) + + line_edit = QLineEdit() + line_edit.setReadOnly(True) + apply_field_style(line_edit) + props_grid.addWidget(line_edit, i, 1) + self.prop_inputs[label_text] = line_edit - def add_plate_row(self, row, text, widget): - label = self.create_label(text) - widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) - self.form_layout.addWidget(label, row, 0, Qt.AlignVCenter) - self.form_layout.addWidget(widget, row, 1) - self.plate_rows.append((label, widget)) - return row + 1 + props_layout.addLayout(props_grid) + right_column.addWidget(props_group) - def on_girder_type_changed(self, text): - is_standard = text == "IS Standard Rolled Beam" - self.is_section_label.setVisible(is_standard) - self.is_beam_combo.setVisible(is_standard) - for label, widget in self.plate_rows: - label.setVisible(not is_standard) - widget.setVisible(not is_standard) + content_layout.addLayout(right_column, 1) + container_layout.addLayout(content_layout) + container_layout.addStretch() class StiffenerDetailsTab(QWidget): From 5cd804d472823f9655ba8cf6f4303e3f99d36ee6 Mon Sep 17 00:00:00 2001 From: Garvit S Rathore Date: Sat, 29 Nov 2025 17:22:21 +0530 Subject: [PATCH 37/59] Update template page --- .../__pycache__/input_dock.cpython-311.pyc | Bin 56563 -> 56561 bytes src/osbridge/input_dock.py | 1 - src/osbridge/template_page.py | 473 ++++++++---------- 3 files changed, 212 insertions(+), 262 deletions(-) diff --git a/src/osbridge/__pycache__/input_dock.cpython-311.pyc b/src/osbridge/__pycache__/input_dock.cpython-311.pyc index 66ecb648f3d407cc4ee4e4d1b19b2528197b17ff..15318aaa22012cb2ad736347de855f75f405c494 100644 GIT binary patch delta 162 zcmeyollkLLX5Qtzyj%=GP%kc*8NZP?VI5=k=Dc-*T#T1DAK39mi213g!sd;q#W@+5 zY(9C#iJ9@>W}X{yjKZ}Xthyf Date: Sat, 29 Nov 2025 17:32:53 +0530 Subject: [PATCH 38/59] Apply local changes, remove Python cache from tracking --- .gitignore | Bin 0 -> 44 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..12829c01a33a63f07077d85ce9d9be3692ae3f3b GIT binary patch literal 44 pcmezWFPcf&6#|eFk0zE(R?IJ*XUr2LSfG2v`6B literal 0 HcmV?d00001 From f63cc7c580e16038a8ec08219575e67a40037e6e Mon Sep 17 00:00:00 2001 From: Garvit S Rathore Date: Tue, 2 Dec 2025 09:28:58 +0530 Subject: [PATCH 39/59] Stop tracking pycache files --- .../__pycache__/template_page.cpython-311.pyc | Bin 0 -> 24078 bytes .../__pycache__/__init__.cpython-311.pyc | Bin 0 -> 176 bytes .../__pycache__/backend.cpython-311.pyc | Bin 0 -> 4935 bytes .../backend/__pycache__/common.cpython-311.pyc | Bin 0 -> 10586 bytes .../__pycache__/resources_rc.cpython-311.pyc | Bin 0 -> 14681 bytes src/osbridge/template_page.py | 15 ++++----------- .../ui/__pycache__/__init__.cpython-311.pyc | Bin 0 -> 171 bytes .../additional_inputs.cpython-311.pyc | Bin 0 -> 105752 bytes .../__pycache__/custom_buttons.cpython-311.pyc | Bin 0 -> 4345 bytes .../ui/__pycache__/input_dock.cpython-311.pyc | Bin 0 -> 57863 bytes .../ui/__pycache__/output_dock.cpython-311.pyc | Bin 0 -> 19552 bytes 11 files changed, 4 insertions(+), 11 deletions(-) create mode 100644 src/osbridge/__pycache__/template_page.cpython-311.pyc create mode 100644 src/osbridge/backend/__pycache__/__init__.cpython-311.pyc create mode 100644 src/osbridge/backend/__pycache__/backend.cpython-311.pyc create mode 100644 src/osbridge/backend/__pycache__/common.cpython-311.pyc create mode 100644 src/osbridge/resources/__pycache__/resources_rc.cpython-311.pyc create mode 100644 src/osbridge/ui/__pycache__/__init__.cpython-311.pyc create mode 100644 src/osbridge/ui/__pycache__/additional_inputs.cpython-311.pyc create mode 100644 src/osbridge/ui/__pycache__/custom_buttons.cpython-311.pyc create mode 100644 src/osbridge/ui/__pycache__/input_dock.cpython-311.pyc create mode 100644 src/osbridge/ui/__pycache__/output_dock.cpython-311.pyc diff --git a/src/osbridge/__pycache__/template_page.cpython-311.pyc b/src/osbridge/__pycache__/template_page.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..943b55b333a54e6b079b9ac9b22e733d123e177f GIT binary patch literal 24078 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 literal 0 HcmV?d00001 diff --git a/src/osbridge/backend/__pycache__/__init__.cpython-311.pyc b/src/osbridge/backend/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b04283e963d3539a0af3c13cf352cf56f03ae42a GIT binary patch literal 176 zcmZ3^%ge<81nEh7nW8}YF^B^Lj8MjBHXvgeKn4F!Oml6{npP83g r5+AQuQ2C3)CO1E&G$+-rh!toi$kt+hAn}2jk&*EO1B@tQ28say;}k2p literal 0 HcmV?d00001 diff --git a/src/osbridge/backend/__pycache__/backend.cpython-311.pyc b/src/osbridge/backend/__pycache__/backend.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a0a91106d2cd27a0e247aa61480a55aa51b3b37d GIT binary patch literal 4935 zcmb7IOKcm*8Q$efD~e0%Wy^XvT0631+O-hNZXD-Xilk-Q6e*LGABvI<#aYpWsaf%=gTXaMIK?|fJ4qB?}9|L-2c{9(@Gl1%-iHWrN~vNkl`>o~zy?T6#egAn{~wMt$1Ao#aD4 zk;*UdH&dB~bgYon0&5bgR|Snlx^rH^MO;_3VY9nZ$5;~0(eG%@rwG+mh4|%unT>z6 zyr>AWvOHImpH%R2LV7Azr6N+6t?4c+a%ovoR`j_quNbkgey3~e8pHDyv4VNNamp2` zX^Rs8XUZgy=hQz>zPIW7Q{VpSvzxv>W^8{dN`B9%`ri?m?Xlnyn0oN~p>aHT5E42Q zrR5&r$bCSJIms+EwLT*zyjWZl8slzU4yLZifC@m1a4N&e5$GJlK+x+L-tiL*W$GF2 zTtsXy8bx6%r9lPJ$b-DAfoGl#`p@=K0>dsFo^N4(-`gobgAOKG%!2h#$LQGByj`t% z-PSOCa8|)#s|6@YFwKGotrj3Q@cO!X@VPwz-r<&hN4);7*8Fa3_@u+uDM#ChcdRvT z;th255O8|{yb~?`j(B^zTI+FJ!|yn3agO%cR$E^;A7la3)h^R!cdDh2&pXLJ+XVY;P_-*>b-A@A#Ix6f@C)@{aNtS`_I zwuY}cY<=KpJM-*DYaF>U)r1JeSSq z`Pp17p32N=CvEZL<9c8c$@m>dU`RLGFO$Pi3rRIxkwk?j>8W|}6{s^%uR^^B^|w$z zfO;M3hfr^5UVPrsIrTjo_%KcAI$hq%=kC6yUgSsGpRC@hXE=w)EvIB zb@3H?o!FUH*}I(u55ipY?2YYDo40wjD5&MS%0B5VX_`F06?~Nd?_rhAb{0GcpKVTk zy8Wz~0eg?twLhrrMrX-if%4=__OBbS3a_uJvAoI_I@>!4zuP?b`QInrV0H1K`o)^c ziroaqniH3|o^4A+w4}1Ai(KS%b8_Z~^v={9pPKnv4Hq}Pt#HS88E%gucS4v*|8Vm} zE8O9`odpjfqc1<-n%};$v$Xs1!rsRVYVMx;U{wv5yUD}ye7Jr2$5A!^AmaeH%Kk4sNqs)@t-5(Dl^`Gr?v0Z?oU1xO$Ddum%!Qc-(0Inp_ca)MM;k-p9haLfmtlvC~9AotG3;EF0z;lkQo$1a5<} zG6Li|b-=J{cxsn9x5u1Qt-tmecFvSReGXC`lf&KE==9iINPVl-V)+EsMJyXxVj;C& z&HC~y#~=@_*2NMJngq0IxIJ$=2;~TJ66&`mxcT83;k=sQO43>ll3c_}=Tsgj!-8K$ zd`r&$?+EL`KHVmA?^rZ5QI@>f$;+7MwII)%uVG#b@%-=W#j45a;dvyLcwQ!FlsrI4 zoDlL+$SFefRk}-B1wwQ;`UglBO;1o6Ud)tE$Va051;}&z-@fmX>brF4^ZG9yQZ@(pg2{T=!iX&QWWrvK5$|J#MDqxm0LyJ{Q& literal 0 HcmV?d00001 diff --git a/src/osbridge/backend/__pycache__/common.cpython-311.pyc b/src/osbridge/backend/__pycache__/common.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e347fcb3a26f93919be41fbde7581701e6ffaaa4 GIT binary patch literal 10586 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 Te|W2Pb+ipW7rx=ax`qD*jir)L literal 0 HcmV?d00001 diff --git a/src/osbridge/resources/__pycache__/resources_rc.cpython-311.pyc b/src/osbridge/resources/__pycache__/resources_rc.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..774b3dc8b3f987aca8d6b0333c45339ff013f7ae GIT binary patch literal 14681 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 literal 0 HcmV?d00001 diff --git a/src/osbridge/template_page.py b/src/osbridge/template_page.py index 2e21d948..c4b74502 100644 --- a/src/osbridge/template_page.py +++ b/src/osbridge/template_page.py @@ -8,7 +8,6 @@ QMenuBar, QSplitter, QSizePolicy, -<<<<<<< HEAD QPushButton, QCheckBox, QScrollArea, @@ -16,22 +15,19 @@ ) from PySide6.QtCore import Qt -from input_dock import InputDock, NoScrollComboBox, apply_field_style -from backend import BackendOsBridge -from common import * -======= -) +#from input_dock import InputDock, NoScrollComboBox, apply_field_style +#from backend import BackendOsBridge +#from common import * from PySide6.QtCore import Qt, QFile, QTextStream from PySide6.QtGui import QIcon # Import resources to register them from osbridge.resources import resources_rc -from osbridge.ui.input_dock import InputDock +from osbridge.ui.input_dock import InputDock, NoScrollComboBox, apply_field_style from osbridge.ui.output_dock import OutputDock from osbridge.backend.backend import BackendOsBridge from osbridge.backend.common import * ->>>>>>> 81a220d3af4603d99560ba55fc7eff99cec8b3e0 class DummyCADWidget(QWidget): @@ -56,7 +52,6 @@ def __init__(self): layout.addWidget(label) -<<<<<<< HEAD class OutputDock(QWidget): """Output dock styled to match the provided mockup.""" @@ -358,8 +353,6 @@ def on_toggle(checked): return frame -======= ->>>>>>> 81a220d3af4603d99560ba55fc7eff99cec8b3e0 class DummyLogDock(QWidget): """Placeholder for log dock.""" diff --git a/src/osbridge/ui/__pycache__/__init__.cpython-311.pyc b/src/osbridge/ui/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e1005eee7fdcaab489547944536ffc0e788b6538 GIT binary patch literal 171 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 literal 0 HcmV?d00001 diff --git a/src/osbridge/ui/__pycache__/custom_buttons.cpython-311.pyc b/src/osbridge/ui/__pycache__/custom_buttons.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..52caf39c227c083d78c8f58b6c47cc338e08391a GIT binary patch literal 4345 zcmcgv-EZ606(=Q1x{@V3W~}(L4jl)n?fNtGHpDBo#dhtaa2zX6Ta@!a&>}6fB}MO& z@>gY`4+Z881C|0UY7`rwhs{mlmp$xZe}*U!kU?O;fIa0+MOt9!)6OL&QL^kI+rt?7 z@Z5XOz31F>4)6Kpt3)D3!IRmXRMaR%{gVu8PpHbg{1TWiD2d`JiIxH?tp)gi7UY8( z!!ufl4{2dOtVQ^UhIph!`KT7-V_F;EMic#@8rKqh!sQvYUF+aGv}^n|t&{K6Sf16c z^VhX5zDrB;NmnPN-q5=FZmoy!(R%q_nhH?MlobAik|J{OG)Pfr@aH{zpM=(_(dgkX zVhcIL)HQB1XPSDJ+f$^BY#Q8_j=9H%BxbnfcOFj09%j>OPLd5yle0Na%u1Y?HWkqK zmKm_l+~(5WR)%??%En~u1+g_6b0Vqt++Cfv)LPEio_8(5;N5wBe?>gdb0!QP_=9-r z{?3l7q(x!@M9Wx_s(RtninuAOF!@MSvhOKbN#BFPx^gJ5>9FL$38$8@sL7+W6HBe{ zW<1NI5hsu`o$$Jn$%?8Iaud^98+7g z#C($Af!n9~5F{h)TwB!BAA0%lg0$)|ro3-DQ3cEiJEFOrW*SU|fhQ?_`B#|zoc@9` zX{)$SXpqz=seJGh=FjRfn#xm!fG_tslh~?0AJl!gWOeQIHD&iL(U)j8?C2{? zfs2}EhcC@j&C(+#&lHd^_cWC{u2s?X&^7d6e-8l)HU zbrjBr3k`YjHJj?ZZb^qnfOHCf|3-%>Gf z%Il39HNLre3SG7N#&@&MQA7$IA*j`c8uYcBbqhCqiO)6byy8yX{(7Hl-sunoM^Pzu z6w62Qh>Y@4m&RO48{8i~{#u{Qw_VJiM8Tccw(77FTzZ1nIs~tsi2Lv@#<+&3p>c2@ zHA#rlrkMUPgY{finn>%aj_0`Hd(-!qW`6LK7S*bJZ(A|tR_UgWB^j1y_3V|>2`oxV z&Y0t7clKL%*byZO`o$bKGkuwO3%bXI0o@7|X0BA&la%^;eRMOxNjb=k`*Z*IQkP^8%?|w*MkS}wz*9jRpfB_B?1SPAOg5X3A*#rb&=oq(? zkg#hyw0%hP13*Haz)674#XoCq%050l4ji0OOh4P6y<($rVPGw?K+X?KvH=@(~ znA|zQw_)GI-iwH9vX*p`XyZ-z4F2^o^>>u`P2_ixGU_Rzo|79E;%vkf5qFN_zia68A0jS?IQnZWqz*e~^gumr(y7=l}57LgO|XFQV~k z^q=ioXwXK3MKo9ekGOJjq?8=7l4Evqtel)KC8w?AT|0Rfd`xx&VSCE#NQoV>*fECf-p+sc~Xu9r~e!;0L8sELOu^;4G7d}?O!EuFEmo2 zfSjzmujk&&c;>8 literal 0 HcmV?d00001 diff --git a/src/osbridge/ui/__pycache__/input_dock.cpython-311.pyc b/src/osbridge/ui/__pycache__/input_dock.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..61702dabc3c1f2bb249030c3c80569af47adea5d GIT binary patch literal 57863 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 E03J2T1^@s6 literal 0 HcmV?d00001 diff --git a/src/osbridge/ui/__pycache__/output_dock.cpython-311.pyc b/src/osbridge/ui/__pycache__/output_dock.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c954e5a78ea046c7ca725b7aa39d2c44005f1d4c GIT binary patch literal 19552 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 literal 0 HcmV?d00001 From f91de37130680abee89a83e50b2f2a128cb124d9 Mon Sep 17 00:00:00 2001 From: Garvit Singh Rathore <78960005+garvit000@users.noreply.github.com> Date: Tue, 2 Dec 2025 09:31:53 +0530 Subject: [PATCH 40/59] Revise usage instructions for running the app Updated instructions for running the application. --- README.md | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 51f7fb97..38f0be05 100644 --- a/README.md +++ b/README.md @@ -49,12 +49,7 @@ PySide6>=6.5.0 ### Running the Application -From the project root directory: +Run as a module from src directory: ```bash -python src/osbridge/template_page.py +python -m osbridge.template_page ``` - -Or from within the osbridge directory: -```bash -cd src/osbridge -python template_page.py From 5f5e1b09eacc15dc7dd1f168138171309abd416c Mon Sep 17 00:00:00 2001 From: Garvit S Rathore Date: Tue, 2 Dec 2025 10:02:12 +0530 Subject: [PATCH 41/59] Stop tracking pycache files (retry with fixed .gitignore) --- .../__pycache__/template_page.cpython-313.pyc | Bin 7389 -> 20735 bytes .../__pycache__/__init__.cpython-313.pyc | Bin 183 -> 164 bytes .../__pycache__/backend.cpython-313.pyc | Bin 4497 -> 4478 bytes .../__pycache__/common.cpython-313.pyc | Bin 10578 -> 10362 bytes .../__pycache__/resources_rc.cpython-313.pyc | Bin 14613 -> 14594 bytes .../ui/__pycache__/__init__.cpython-313.pyc | Bin 178 -> 159 bytes .../additional_inputs.cpython-311.pyc | Bin 105752 -> 105752 bytes .../additional_inputs.cpython-313.pyc | Bin 81031 -> 96168 bytes .../custom_buttons.cpython-313.pyc | Bin 3757 -> 3738 bytes .../ui/__pycache__/input_dock.cpython-313.pyc | Bin 52837 -> 52816 bytes .../__pycache__/output_dock.cpython-313.pyc | Bin 16975 -> 16955 bytes src/osbridge/ui/additional_inputs.py | 6 +++--- 12 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/osbridge/__pycache__/template_page.cpython-313.pyc b/src/osbridge/__pycache__/template_page.cpython-313.pyc index 61b9f78314427c81e54d05c6762fdda3a4fe9402..3a3b30751f18e953e450a930e73f16fc6bbe568b 100644 GIT binary patch literal 20735 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 Wm@6zf8h_#B|M2XpgL5{~&i`+3Y1e`P delta 1598 zcmZ9MO-vg{6vt;4du@ZifnXbh4GC@Cf)i*`6p~mCAOdy_q^6-CR<{Ok?QO6Zz1<}t zYUP|N^%gp+RP|Q%QmIldzV%Y6r(Sx9noBouXr&%}iv$r-duZp4110t0=QnTu`{unj zAOCzq_b&SPeZEe@e?N?$(GuM|{z(5{eDozLkOIvSX#Z27d@SX5t_(Gj=<3LIWxe3d z`oNdPpg33vO}#@%$xTRcV3=HJ z$0A8mp%t16w{X0eaZEU7ZDOOD)c-#w}CdEG`+-pp`3XQ8&t^in7K&b)FfF@}=y) zS*uptp1C@^it}b#QM9^dDoULF==_dGSydj54RfJEIo^cRoX~rOD0?7BJtId$c2`;m zdPTtNdIeK$oh8lRr_M=@2x@Raa3WlY7@`Z&kBB1%IBZ8%E2}WbO~b*FOeXKO7Zw@! zo?PHK-w9NgIG)`jyKdj-u7~c(BX?{!6#r`F*ATmZC4XJy`4X4F%ZRi1#fhrM(TNZf zL2f+zjQr*}edq`|d-e$jhzKZI-wE}yOy(_bYmSd>r>U76YV+&NFDELs<;@bPbrYsg z;x|-tRjZfv+bgIQ)!Hu6YfDQxA;Cp(5rJ~Jj1Yate(?^x*n%0$F!fqH-f;`Aaizij zxSFRU&2!U#JEc~fjm-}yjst3+@N%`E5Fc=~u=tk5>hn>S4-K&|<|i5=&pGXmTAo+6 zO4YPorE)p1)-AC!fZG8w!h^ugvpeT?(C+GWv$O%&wRXJ$UT>b4SF?DUWwUAvN9oS7 zqkW1(7$L~D1rtwu%lg{K>bluJ;J7S|A<~HdAG1kxBIXDS~4T$oiBFNLrI z+ks7hIyb*1thtzrqS$sTifE3KO29|xkaUA}FI`%&{ma`0es$hR-!Nx%P}`Tw%O&7L zo$tTtYnBFn^o4a}O*{>0Pz~LJb=80X`+g~&z`eBlj;Adx%{PKSK}wllYzVO@OM^7b z0?Pvp@g9ilBMQKL+r1K(O%y1~abK?KWotvtz&4lQ=`*%Co^()3ACu0<#B(TlsB}m; z_7R6Ygibsp<9j6WR3>!vPcphk(tG6mE*UwHyJ_H+C?(B#1=`+50w2QKw5fgXc0 diff --git a/src/osbridge/backend/__pycache__/__init__.cpython-313.pyc b/src/osbridge/backend/__pycache__/__init__.cpython-313.pyc index b0954da4d4b7199298514bf181a6cdcd8789439b..d847b511f849e5f9ca31d0184634d1ce95644fef 100644 GIT binary patch delta 55 zcmdnaxP+1SGcPX}0}!Mq>1B#eej&V;cD$6X1amg>w%gIknDUR_k JcAA*)0RXS45sCl+ delta 74 zcmZ3&xSf&rGcPX}0}yCTQ_d8g$ZKdA=4=%cTAW%`9Fv<-T$&N%pO@-Vlv$P<~l07|?W8UO$Q diff --git a/src/osbridge/backend/__pycache__/backend.cpython-313.pyc b/src/osbridge/backend/__pycache__/backend.cpython-313.pyc index b59d05147c9d8b5f92252d1245a774918bc407a8..43637db051fa1c8969be399d27d65160c7ef22de 100644 GIT binary patch delta 56 zcmbQJ{7;GdGcPX}0}!Mq>22hm$|&dLY!wq)oLW>I5QYDVkOVXkNWvBZf}@})0TEPW5d~M2<%lAn0!c(53m!nlZ2&ji!I3A) zy$|43O7Et$%JLCnd1IATwmySZ`7i#jtGmzCOxKzIH2ACE^~2@LRdS{#>!=&>ZQKSIUC3h|@|h1e-6&uI3Rx)q z9_jbOLm#{>f{(>0Vu|QVQOq)w&@b9@(FR0Y0i%?aD1$#}Ihs#${_tcA|q*vUQE#X8h*FKW3@UZfto8N?noppK2$%l+8LCe-r)f^0?u zThPdZ*v~^~;$a+MtAS=7K?~b(knK3c4jg7DT6q*l*o8J8LpzV7gWc$4503H#x_A=D zcnZgP8r?jD9-hSs_TnVZ;S|r~H2ZLd{W!}3^l}jA2%P5y^l=FNyodo_!XSqMUd9C) zBN*Z+E^-W)cm>0}ip#u)5sqV&6By$pt}ukF4C5Lj80U3Na0-*Wfe@z=<_sd7#dSt8 z#Tafdj%g+^!#T|ICZZ%_h|6^6Wts_@=7MlexG20ipc%X+0VQ!8^H`9^J6Oam;gaNU zOa89#j_@9qa98s8aS!)}5AXmFg%71}NZk-F3zvl}!WFFIk4KMjzT=@d8_!6)AO5)de!#8-# zx8mk?ar3%xL%1P)CwzxZyf0HT8qFy)8P5LTs;ZE+x~Ya_m$nWoHDOn3^zWwqcXSn0 zy4mB2+N>@!>1k-5oCwWE;^DDSA{I-;>*toNoKPYjkAxP&@^6b~LsM$rC&9A%<|tSn z`Ru6tlJt0sTt$|%EtUu`Mk5{kEp_Qr^NkL(v+AQe?acMtQmQyzp*&5?`QMYl>a^yu z0z)ZPW|g$0l+W_Er&NJu_UeA8<(If5Z8KN&8h6^J0^_1BwaUYP?_^5P*8b-M&6M6{ heyy#mOxu+=s-=|MDsD#>=&sAnCva042+ z5n*n^R&K^N)}xVIu$>Lq!7!S*70o)gVJ92W!tH3~4z#feyV#7~+=+I!po6XGWE;A; z3wyX5-E7BRcA$ry*vBsPau530js4t<1MI;;?!zJWqMv;@%>5YP0YrEZM|cQ>?8i|a z#xV}yI3qa0BhbM?oa9lQ;xU}&ah%}^oFy>ClQ_pyIM36#z%wAvVwgj?$a9GDJTCD9 zMo3)dFh+S1F-9@QOSr-jjPo+CaunAXL!4v0BBwhcr#UXCIVrp-__5@6x$WgEwwAGR zPQ`}X;{QsEChtV9O{rvtrfJK0ZsTk4Yjx6=4!Qp-_#a zd_$|L|D&l&5@j?~l69cN6d8Yq+Ktb_Z-2;MW4+02<%(&hb97azAWyd diff --git a/src/osbridge/ui/__pycache__/__init__.cpython-313.pyc b/src/osbridge/ui/__pycache__/__init__.cpython-313.pyc index feb9c0ae30f0fa85e9b0cdc14d51d7b036ebd1d2..bace122245f6c5428459087c69e7624e89bd72a8 100644 GIT binary patch delta 55 zcmdnQIG>UCGcPX}0}!Mq>17H`ej&V;cD$6X1amg>w%gIknDUR_k JcAA*)3;?Vo5pe(j delta 74 zcmbQwxQUVXGcPX}0}yCTQ_kd@$ZKdA=xh}eTAW%`9Fv<-T$&N%pO@-Vlv$P<~*07bkR0RR91 diff --git a/src/osbridge/ui/__pycache__/additional_inputs.cpython-311.pyc b/src/osbridge/ui/__pycache__/additional_inputs.cpython-311.pyc index 4a7a138b0599182b1f4d3bc4564e1e32179311ce..43c81d736bcd088894f8ac483d1da2de3d6073b9 100644 GIT binary patch delta 34 pcmbQSi*3d(Hm>Eoyj%=G@HKNISDe7)%)9)}Eoyj%=Gke;-WD^7qlH95&Rr8!q%d#(WE>K*{T@(QW| diff --git a/src/osbridge/ui/__pycache__/additional_inputs.cpython-313.pyc b/src/osbridge/ui/__pycache__/additional_inputs.cpython-313.pyc index 0ffc8db4eb1ea5de87c8fcfb39c7392f97f09f72..b8d0915c53933c9e49165debcf7106faec673800 100644 GIT binary patch delta 25390 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�vL$_a&;J=r4X@cvQlS+eM RO4gGjTmJ-Yh6m=i{x5K5(suv= delta 13778 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 diff --git a/src/osbridge/ui/__pycache__/custom_buttons.cpython-313.pyc b/src/osbridge/ui/__pycache__/custom_buttons.cpython-313.pyc index 819dc90da32037f3c913889148b5ddf0cd3b74b1..b8a250fbd51740abb9bed19e54c0cc64ca45abd3 100644 GIT binary patch delta 56 zcmZ20J4=@PGcPX}0}!Mq>22hm#UIb!GtEFB0Vd delta 75 zcmbOwyH=L_GcPX}0}$LaQrXBoi_0+F*(xTqIJKxaCO4zFG$Y18FV&?evn(~nCAB!a dB)=fWzu2iLGbKGWCcii(F+DzM^I@**%mAJ@8pQwr diff --git a/src/osbridge/ui/__pycache__/input_dock.cpython-313.pyc b/src/osbridge/ui/__pycache__/input_dock.cpython-313.pyc index 3d73a355ee84497e07411aa4fa8577e3a61b551c..574239161f09dc82c87c2cb13a2236e1acca9896 100644 GIT binary patch delta 130 zcmaDlhxx)BX1>q7yj%=Gke;NMx%0?IK22sh2WP98(Bjmh;u!bDqO#1A7?=F=yqx^R zl;RlwVyDd+%#{lnuWWv_q=lRD;pX%WqWsKHL=`qS?lq7yj%=GAX%lFxp4nRK22uBU}vkC(Bjmh;+Wiw;?j&5|GZR}qRg_? z7?;%I?2`O~82@6YqRf=^)R_F@l*IJ-q|Kqsl?xfKZa%-Hg`4s5=8z4d{H$Nt7{nAe z=j=D)U=-Ut|F|GCqw(gArxr4@RdccGeyZ9WapozbaHbGr2;&6yuMA*DktWa`01XZ_ AfB*mh diff --git a/src/osbridge/ui/__pycache__/output_dock.cpython-313.pyc b/src/osbridge/ui/__pycache__/output_dock.cpython-313.pyc index 38de5e87922efc2dff150a05ad127e301f07619e..1e45ede79382d8050c0cedf75b22714380f60170 100644 GIT binary patch delta 89 zcmX@#!nnJIk?%7vFBbz4q$lZR{&(2O_nAq~(b*~{v^ce>IL1A(s4TN2#wEWzFDE}S sr8vgF*lDv7vw%Dgw|{9#L1~GCOMY_pW`8ApVV2m-lE#~xOwaNF06Lc*5C8xG delta 115 zcmdnp!g#)gk?%7vFBbz4ysK8tWN_Tb_nFBs#Mvq)v^ce>I3_ovxHKciKQGm#D6=dz z#wE2lyClCL#=qF9C^IEJH736}B{4ldX|p`DfIL5sQ(|#uvVvz`L1{^GqRZs<3XYrW Sl->xj#9fv&*_>y3mIna&@F>Xu diff --git a/src/osbridge/ui/additional_inputs.py b/src/osbridge/ui/additional_inputs.py index 7350ede8..1203a534 100644 --- a/src/osbridge/ui/additional_inputs.py +++ b/src/osbridge/ui/additional_inputs.py @@ -118,7 +118,7 @@ def apply_field_style(widget): border-color: #b5b5b5; } QPushButton:checked { - background-color: #9ecb3d; + background-color: #90af13; border-color: #7da523; color: #ffffff; } @@ -1866,9 +1866,9 @@ def init_ui(self): font-weight: 500; } QTabBar::tab:selected { - background: #9ecb3d; + background: #90af13; color: #ffffff; - border: 1px solid #9ecb3d; + border: 1px solid #90af13; border-bottom: none; } QTabBar::tab:hover:!selected { From 9171259ca221b0bf3dd624a857850d14d4bb8dbd Mon Sep 17 00:00:00 2001 From: Garvit S Rathore Date: Tue, 2 Dec 2025 10:05:20 +0530 Subject: [PATCH 42/59] Remove remaining pycache files --- .../ui/__pycache__/__init__.cpython-311.pyc | Bin 171 -> 0 bytes .../ui/__pycache__/__init__.cpython-313.pyc | Bin 159 -> 0 bytes .../additional_inputs.cpython-311.pyc | Bin 105752 -> 0 bytes .../additional_inputs.cpython-313.pyc | Bin 96168 -> 0 bytes .../__pycache__/custom_buttons.cpython-311.pyc | Bin 4345 -> 0 bytes .../__pycache__/custom_buttons.cpython-313.pyc | Bin 3738 -> 0 bytes .../ui/__pycache__/input_dock.cpython-311.pyc | Bin 57863 -> 0 bytes .../ui/__pycache__/input_dock.cpython-313.pyc | Bin 52816 -> 0 bytes .../ui/__pycache__/output_dock.cpython-311.pyc | Bin 19552 -> 0 bytes .../ui/__pycache__/output_dock.cpython-313.pyc | Bin 16955 -> 0 bytes 10 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 src/osbridge/ui/__pycache__/__init__.cpython-311.pyc delete mode 100644 src/osbridge/ui/__pycache__/__init__.cpython-313.pyc delete mode 100644 src/osbridge/ui/__pycache__/additional_inputs.cpython-311.pyc delete mode 100644 src/osbridge/ui/__pycache__/additional_inputs.cpython-313.pyc delete mode 100644 src/osbridge/ui/__pycache__/custom_buttons.cpython-311.pyc delete mode 100644 src/osbridge/ui/__pycache__/custom_buttons.cpython-313.pyc delete mode 100644 src/osbridge/ui/__pycache__/input_dock.cpython-311.pyc delete mode 100644 src/osbridge/ui/__pycache__/input_dock.cpython-313.pyc delete mode 100644 src/osbridge/ui/__pycache__/output_dock.cpython-311.pyc delete mode 100644 src/osbridge/ui/__pycache__/output_dock.cpython-313.pyc diff --git a/src/osbridge/ui/__pycache__/__init__.cpython-311.pyc b/src/osbridge/ui/__pycache__/__init__.cpython-311.pyc deleted file mode 100644 index e1005eee7fdcaab489547944536ffc0e788b6538..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 171 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 diff --git a/src/osbridge/ui/__pycache__/additional_inputs.cpython-313.pyc b/src/osbridge/ui/__pycache__/additional_inputs.cpython-313.pyc deleted file mode 100644 index b8d0915c53933c9e49165debcf7106faec673800..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 96168 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

?AJe!kvhy9d7juy`E^0!)&k34 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 diff --git a/src/osbridge/ui/__pycache__/custom_buttons.cpython-313.pyc b/src/osbridge/ui/__pycache__/custom_buttons.cpython-313.pyc deleted file mode 100644 index b8a250fbd51740abb9bed19e54c0cc64ca45abd3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3738 zcmbUkU2ogg^-7|oD_OE*#fm@c&~CR{ZTywl+v08A61#T7eAp7Lc7>G@v_#8HY0^8S z{E=cnpN90Q!MXw-FrY55VcFXr_cKOVgA8Io2kgmjE|M;N+_|J^N{zD))5E>z`<#2u z<+(>s)9EC^Uv+&#)e?mKiy{6m+612d27ynBNPvhTF?>5Dgdr?MAR=(U2~mg&F^CCq zhzk^`kbs1cgrv{`9Xykd+)fE;NV_n1yHn_bF5v=P5W1mT$UsK82p5GO=n=Azb!DQr zFA2TSEA&C1&=37Q=_8{=j13SmUW&{{yqI91N^^;aYsvYVX&GDmdd;#7o!?a}RmC#- z4FmXPvm#gdrQ)rLm-PxSmn{|LJv9^ctWCb`nKG??O))2uC(KhW z>BRHzx>lWz{7TK-yy1F6Uqw^op9cAVxn5rWzC7Y3Ml&a zj-p$L7L>B(Wb!LuY%5^xU)R+w&v`2Uwrr|;b-o5WN-lz;i-xOltBj%1{0c^4DNy!u z>aAx*{~_YPz_OPJgs}8sF$57YCWcF#7%6cxT#?L#L~ejQpbxkQA&81mh!x3CLJ)tA zV8j;|jPmktig+3)~XUx@9k=)1F zijmonN90M76#Qt<E#`w9y#enZaC~Ol0mp|#tk7)O z!_arMaNg--fp3&J@AUB&39c~cTMzbPG>}+(#itj~@~X#*vBG7a9_%f3;{&P>1vXLp zyvS*r#Cee?S|rYoy!G_lx5PN=M;7cY_DSJ80o{j+EtSPnyzbM3-LG;X7tnmj=jm}V zs#Cl>Z~B~I_hky>0nLYe-nVE@`trfBHFJ)i+1`86!nRzgV27RKr>D+iZ(zS2 zH?hB?!1PN6b``a{Y0dHLhE{2J@vF82wPa;()7Vkqf6L7eBBC!_z@V$2bXHU|9bv(ygAnWF~4ER31%&n7Z`q9{v_|R;37fUS9Vg zr6)aj-dw*EHEZ~AI#iNWU9}|1NtlX-Z>DJgbh4CWRt$V7vCKDdcJLTZ8fBK(AK=q_ z4PIkY+-hX2=yMh2%wbPpPn3 zz)9ex@}7RcnT)vH>l(iPIKSYu;ezuBV$dlUd8di7z?-uMWw-7ohV4XgDzY}6F05x; zD4eEjAQ#x(bGlITE*^iYwuPRp&0N~yOhwxO*5P4@MG^A~>cli}>Pawt_nJ6K_Nq&6 zC7ZD4GwHtb9vt6lrXb$@W}L*9s!JX-AxULTHcd$~SyosD{ojYq!u(vRXewZqmSosb ztuuX>#;P7gle%Hk>+w}5bx=ts~uv5Z~pieH5K24HDSA+Jkxclq$RkV6( z=wRWK(xc=t%`_;}{X_Uqou7BQ0U(99DStTZo}h`HlISQ}i=wquU~QmIU;8IJ#h^_G ze}Ch%<=-vW>6K<)9`DrY@G1J@8`&{CJ9dx%xMzCjuqnSI6nSwo2IGS0tGpmo{$C(~ndWP+uH=k7Pp4q>~?Vg{q z++fm&Y&vxC-rwo#PqA9$&mu`ETSld(F*qejzpTkxlhY+h8!DKV zrs|4rAUk4O;N6KVUOEd0=CUMP7O2>k6ugH{x_Kqbil#|YE(G)JhOp7>XRv@Puo=r; z*o`&Wck=bG$(P}4(Xj*Zl;HM6e9pFivjrA?>3g|BCoW0&3Zj$lm3>jIDBqvRTlm?r z|1Q7=U|=ΜJG2BoJp1n`Yg90B*--LlC5Jae4wHXARo)x=Uxknm0gk+ZnK-6)v!a zjj6!8KCnR)SU+^qfyz=%g)C!SVhfu(y+bcC$UP8CghPMqCH*Z}MU-X8yoSqZBoqpL u8IFXw=QI)uKPUe36(yl7Uy%C^a^EKRzaUdKnR>>}g~lF#@D;($HT@s1w^SMc diff --git a/src/osbridge/ui/__pycache__/input_dock.cpython-311.pyc b/src/osbridge/ui/__pycache__/input_dock.cpython-311.pyc deleted file mode 100644 index 61702dabc3c1f2bb249030c3c80569af47adea5d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 57863 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 E03J2T1^@s6 diff --git a/src/osbridge/ui/__pycache__/input_dock.cpython-313.pyc b/src/osbridge/ui/__pycache__/input_dock.cpython-313.pyc deleted file mode 100644 index 574239161f09dc82c87c2cb13a2236e1acca9896..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 52816 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ⅈ3(5tjs8Eolni}oyHUu5pU?Yu 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 diff --git a/src/osbridge/ui/__pycache__/output_dock.cpython-313.pyc b/src/osbridge/ui/__pycache__/output_dock.cpython-313.pyc deleted file mode 100644 index 1e45ede79382d8050c0cedf75b22714380f60170..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16955 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 From 0e728750f5620e8b015f6efd52fa2e0780aef9fa Mon Sep 17 00:00:00 2001 From: Garvit S Rathore Date: Tue, 2 Dec 2025 10:08:35 +0530 Subject: [PATCH 43/59] Fix .gitignore encoding and remove pycache --- .gitignore | Bin 44 -> 19 bytes .../additional_inputs.cpython-311.pyc | Bin 106296 -> 0 bytes .../additional_inputs.cpython-313.pyc | Bin 81547 -> 0 bytes .../__pycache__/backend.cpython-313.pyc | Bin 4489 -> 0 bytes .../__pycache__/common.cpython-313.pyc | Bin 8621 -> 0 bytes .../__pycache__/input_dock.cpython-311.pyc | Bin 56561 -> 0 bytes .../__pycache__/input_dock.cpython-313.pyc | Bin 51502 -> 0 bytes .../__pycache__/template_page.cpython-311.pyc | Bin 24078 -> 0 bytes .../__pycache__/template_page.cpython-313.pyc | Bin 20735 -> 0 bytes .../__pycache__/__init__.cpython-311.pyc | Bin 176 -> 0 bytes .../__pycache__/__init__.cpython-313.pyc | Bin 164 -> 0 bytes .../__pycache__/backend.cpython-311.pyc | Bin 4935 -> 0 bytes .../__pycache__/backend.cpython-313.pyc | Bin 4478 -> 0 bytes .../backend/__pycache__/common.cpython-311.pyc | Bin 10586 -> 0 bytes .../backend/__pycache__/common.cpython-313.pyc | Bin 10362 -> 0 bytes .../__pycache__/resources_rc.cpython-311.pyc | Bin 14681 -> 0 bytes .../__pycache__/resources_rc.cpython-313.pyc | Bin 14594 -> 0 bytes 17 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 src/osbridge/__pycache__/additional_inputs.cpython-311.pyc delete mode 100644 src/osbridge/__pycache__/additional_inputs.cpython-313.pyc delete mode 100644 src/osbridge/__pycache__/backend.cpython-313.pyc delete mode 100644 src/osbridge/__pycache__/common.cpython-313.pyc delete mode 100644 src/osbridge/__pycache__/input_dock.cpython-311.pyc delete mode 100644 src/osbridge/__pycache__/input_dock.cpython-313.pyc delete mode 100644 src/osbridge/__pycache__/template_page.cpython-311.pyc delete mode 100644 src/osbridge/__pycache__/template_page.cpython-313.pyc delete mode 100644 src/osbridge/backend/__pycache__/__init__.cpython-311.pyc delete mode 100644 src/osbridge/backend/__pycache__/__init__.cpython-313.pyc delete mode 100644 src/osbridge/backend/__pycache__/backend.cpython-311.pyc delete mode 100644 src/osbridge/backend/__pycache__/backend.cpython-313.pyc delete mode 100644 src/osbridge/backend/__pycache__/common.cpython-311.pyc delete mode 100644 src/osbridge/backend/__pycache__/common.cpython-313.pyc delete mode 100644 src/osbridge/resources/__pycache__/resources_rc.cpython-311.pyc delete mode 100644 src/osbridge/resources/__pycache__/resources_rc.cpython-313.pyc diff --git a/.gitignore b/.gitignore index 12829c01a33a63f07077d85ce9d9be3692ae3f3b..7a60b85e148f80966a550e5ab6a762a907c69ca6 100644 GIT binary patch literal 19 Ycma!#FQ`mTOwLG+kJsnY(gSk107TCQK>z>% literal 44 pcmezWFPcf&6#|eFk0zE(R?IJ*XUr2LSfG2v`6B diff --git a/src/osbridge/__pycache__/additional_inputs.cpython-311.pyc b/src/osbridge/__pycache__/additional_inputs.cpython-311.pyc deleted file mode 100644 index 60f02e5d7abcd58eb8a4cdfb5da88349afcbb5cb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 106296 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 diff --git a/src/osbridge/__pycache__/common.cpython-313.pyc b/src/osbridge/__pycache__/common.cpython-313.pyc deleted file mode 100644 index 992a4163011e8903d7f006e9a0591309c69a7ec6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8621 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 A%K!iX diff --git a/src/osbridge/__pycache__/input_dock.cpython-311.pyc b/src/osbridge/__pycache__/input_dock.cpython-311.pyc deleted file mode 100644 index 15318aaa22012cb2ad736347de855f75f405c494..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 56561 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 AwEzGB diff --git a/src/osbridge/__pycache__/template_page.cpython-311.pyc b/src/osbridge/__pycache__/template_page.cpython-311.pyc deleted file mode 100644 index 943b55b333a54e6b079b9ac9b22e733d123e177f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 24078 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 diff --git a/src/osbridge/__pycache__/template_page.cpython-313.pyc b/src/osbridge/__pycache__/template_page.cpython-313.pyc deleted file mode 100644 index 3a3b30751f18e953e450a930e73f16fc6bbe568b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20735 zcmdU1YiwKBeZPE(m*k~H$$C&vUp@SwWy=rw5zBd4w(L0aD-y-Th?H;?NlA1lQaP85 zEjlgIVQUq%S=_ln)nz~xtXS-=1B?RO`k@<&eHr#?BE&@MjawL)x6U61D3LOE-}e8X z`=*X8C+)E93Oe_mbI(2R^FP1;@B9zf{C+P7*T0VUq=ug5xPL*9cDdAtwY@fud!Gw) zVcU6*;N5=SHfSgIK?iXRI*D`8MO=dwq+-xb+=D#f2R+0y=q27kAMp(eL>TlFzlh&C z&Q}grkt+J^RGtR|BrsS_s%e<(e9hoCvW-4hoUa|MBX#uIeZGFMfiw&TNpP@{G}18s zeA8evX{OJf^DTp|q?JB<&$k`3i(HWF;KIHpE-Z{WjyRN>knJ?o522N&&}JH11)%{` zs7OPrA+*L6+8*B4#F37%&Twtt6^@JX95>d{!5vYXO5`SOleSRZL-bz?39>NMKQog~ z#bdctHY2--UQQ(@lR4Qp^wO#9^$W4tY#u%53{S<-h1hs9E%QSclbQUf7?C|gVMrmD zOA?3~P8E_bW+A~@@VuCprcUK^IY`+vbb2Zoe;v}nNO6))r~64VCRYrdC9&yb$R^u| zav=w)f^x~Op|h!U5+aTyujj%!h&K%pTthF!vl)00NK(q?NjxbRH#A%n^IoMjT-~aYneqwy>j#yUG(+*hwlP+%q=f?&3l& zxpE*sJw1E6e?V#4hwYF=q3OkRES{XorV~jbPGpG)@5O7h?c*?*^$Q0E{;w^9^Q`TC zZd89Y;)rms!W1yNc2t`XT!cHKrjoIZy0uTjHa*lHu@8H+aN|BoCmZ^Q1tU~@(!Lfs z_!>K^>J%tWv{^-F~ykVLXJy?AXfb4R3?$V=G|>-lu+TmS2SJ9s1t8k0`dJg z^xY)M<}-@&RiEI7^4ZX^Z4=O8{^Hy5{Xo1vQIpG zVCMS9*Av-HZl46>98!9U6+tV<(W6JLUw_R~q>x8;N% z<&x7g=~ylqorz5*duC?Ib}Vayb%^(9)#-wl5=bHLH*a(Ic>g=@A|EXAP4}u>=0}#Q zJLW3x1?m?Y4lMgyydLjrJ?E)f z=7W&VeBHL8;wq96Zteg3(@Es`B++gQxyd=u!1>KLxnDTD9ytRpag76qbZb#8QUxbAUTYa}ww5X&*DJ!VG52A6xB?`Nlkj2p zmUKYPh0=P&U)KM9JIH50(UrokymtY{14tOEEsaVLTLUytPBhsOVi;?)Y zGWO`e-Da$2`qb-#ND?~Mskg!l$}F3Iy2S6+eXDjC&M-I3umI=rzbCvSyjw{}t);6g zAU(Po{RAWktVQ}tWnOP0wqb2xLv-y5+myc}D0|?7jn9A zS`26r$MCj|<%*a~?ijAy7(;6v+NP`&%x#L(o?hBl8CS$L+^B^b_psMgGS@Ilxw31t zMGF|lVO(}uzGl_jXT)IibcJbjRcJ9Y?ueV&2X5#C_%r$d;;{sco)Q%_D6q?}u%P9Y zhj}*h4v3@9vMAt@~gTH_gd!$>}d4C!FJDSH#pxP$yRJn)}JU#rSXy?<&gx zyb*J&*qC{k=0>|R*Q^3;%ANUh~A>m671CVmJm5p$^kv)%1Gr`Ou14^ z0~8-FRDJ0o=tJB?#I_Gnmdb*%2%JmC02vnk=rQYr66MKOisJ|R&mQdEbfqA%L@F-< zdW6No8p*mmJgkHw5)}6ZpCaN#RB{RjRe*VIDwRu~0QJGZqrH%#5^ybmdED?PaKYzg;s~s%Oapms?t0OYJs9vD9vpxBK*7I9%Qfn2W9h90VOeJb089 z$K3FsQ#`IKO{#F;RQ63!`fhA2*G5g|*!c0M4oqy8aH0PBWF|=fj%%GGj>N{($wHMW zd?rhBp*pHq$Ta{lPQz${zLhS<$Yd%b$zIeWW@2$rcnGe|awP5JXTk zLU8$~$_YV{D0@NWK;;}zB0>31lUH;Q7%Qm)7DaA>)Xrr|ssP!>(u!^lq=7;aC~8fO z9@?M27MqnWPfW;l(Ku-Rpl3*chk%)gP9RGlH$`W%GkL0j#ME)A#gK!>y96Df`Wp|F zK`9qOWDu&NU`qB)$5NT7sw8=GsT`;bcdsE{ag0c}L@QQ#x6F+mwq z{OUKAdf}8}*XsW9Y+_c+Qc#Pj#HmqzmKdOe0C}R&7P1d$51fI~@mxmsk|g#lK1&Ez zaLk%61s$HCy<$R#Dx7>C!leKR7EsIT+ZVc)>UYigR`_kp{LUi3bK#BK?YD>S+DiN( z8rD|i+vZ7$?|4w#eCvfd&kA3?%!@@{T&OAWUDUU|$Zub$Eb;qwpMfaX16;6k;rLR+ zzByrquU+PM75QDa?YH}Hle-5?{E-LsE%UD~)px5UXfN{Ziye^?{}PQ%Grv*dyQvTJ zDe>*JSgl3Abv^|s_Ca;at=74U6~1zr$FCQn`%$KR&g*x4*czzr>%SVJ$_z zWw900f6yp?&^T9l4@!4wDcC#bU*YSP`Q1f+_w8yZ-5Yn?OZ;IPrdRg<{@&&NFBJE` zusArjwExv2zjv;Zf@rMM;%>my&(II__PF1@f4TcivHQ%=r=wHVBs<|Le5vZp7xS z)GGkj!=I75NiQ(CG{n!WI=M}?b~4#8Q>mZ?3~Z;B5O7hQ))j_MG3_1kGTYmPP9cVl z__Y*3oi<@ch_MCwhZqd>ANGZPN7OMQauA2r24?KXY=aR~NkIQ;HOi@$IrKg(5 z1hZ5|UVx`P5ibxxur+X%&+yO(9Y*Q3C#F_m(oU}II&23*C~!&kD_}UREV+fhwICFQ zr&mc6H3|#FY~)zuiNsUK4qHJCE>#LjGjJG_OEsXWM(--9*HX4J*o+h$c@CV1NM|bm zy0;vxF>s`)5x0jD_;*6K?L(TC0+5mHZS`gU4AV71T&n8&D*(LPqX zx;K_MnAo~0H=t2WPpjS0#IYDlJJ|wQTN~K~XstE=GVpfd2>w~CW6kCv3Yfy~jiaDb z)v>IajD@Yjl?Y|(+5|Yy+TZ6!Y{SrT<(I)-ffQq|Ul9&0{y8-Cl4g0tjs*RP{8VKgv%sfX*3oteOBpJV~K7-L;?_eH8qoZb5xeS=`T{ zY6b2P((Q^1bbErF#gD1x;Z~}dfELPTGBEE#K85yt4Wh^{1t+7%o+KKE_D@mGUGUNmX9{}lt6bn+Y>I6_peH3CQpa8}@ z`vMdX5VeXMSM5Hmksrw77S2<@%+mzC8b+sx{cC__p_sR()IxR?`HlrZ`;Z5~QU&8* zDDfAq82>?i>#fwB4|S66rTQNA+sEo8)&&03Y4<#}%5xsb1ox7bd7ANYMAlWci&gE5 zd^;WC1ZRW=;o=C#dAIf}c>1d7*4TkHHY_M_tYccpLA@-TwS$R2P0a78jukZg5#iD} z4e15kA!EOmxku~TuOnALl{)HSr(i;`b{0O3Sb28_g55G*8;h|tkUx}Bm)Tm)DVL6Y zRd$&rv#BR^c9SyQTlIwNEA@olJ5XPQ2xS+uC)}I%1S17gPcSuwh#5DsV=zl*N|)Ue z<}tXjClGzJdcuS+!=7#2xg^JB+@rdlq4y*o;hEo?wE{PMe*nJsjOtp5!M|s`5ifJY z&^$7E=GZmmaq$qx`8dGS70l6Ss?EhoH72{Y^sxrx79|S3Ku=OzF5`>%wk$21BiGYl z&IlnQFx$~o^04q*x1;dH?I^G`Ijz^}K0uw;I>z0+Q(*M9p~=x5hOOo=yY)=kAnXHF zULxa=ZNYspIXw;*A%#}6c7R47I#}-Q?LG1E6^JZ5`qSw`^#w4F5Yf0Om7yk6eKLP0 z^JX%g1v?H@IYJIFNV1>3F554{|G;(Xn*|^I56n_u0etX3P$**=5JY*mZDHnL;b_wP-B`{Hz$srP9EwVl;6WZ$M>Y8VV@GmQl8is{m`k$EoT3 zH1M9eDT0kH3)8f=Kw5%nl*|Kr`vRDANCc$~Wpv3kjOIoA4*m8B#~XPaWM^B380eXr!%rM2gq2q$H!%V zLTL;DbjY{*G%u!Boj0lk8h~Y7YM{g&ke7%j7CQkCoukMJqySH(t`bP{}1-lfYf)&OkCg=R(_rgO=e@eN`P{AOP5VlqW;q}h1Hen3ECu>k z0`-b_q2`l7*Jp07u?=vXKe#Nk7lrnPou3Gy&#JikVH+AKXeL0-04N^>+ZMMUTnZji zv2tgT?_BIUUE&7}{M04-> zMA!jEs_R5(34;JDTjE6{16q(4V06zcwVYkVApo7PaKg`*_;Y&1#l}4!^W7AIvccyc z!)SaTZD91J76Q}@3@Gej$9}s;x`8nS)6~KYo;FEIU(^AdvePgvpCB+@m*mPYnyN`T zl8@)|Bv}x`xnwde4$!TZf^Ps+TGb;9;rzHB5u{mzrmJfEGWl_;%Tb{da+-vSkJ`hj zn>`*WKdwA#25X`+yDvh6$eqmDt>vJQHZEvI>3N6^s&9SMa{ZxV{h`I)(?8EFz7$_P zlUS-xqKcsnY3#)NwZY}u1I5|{ciWa~PvTyO_O|h3zEkNqGu}bzOSOgpU)SL4GDLZjGTM zQls=?FR<%KTfs$BVYPt?&9U2H#DrL2Z-B*%n-Kq~&VXwzGc6e7WkhG;H)W?c^ynt+ zw4M@RI5wYej$zk%vvVBS{sR8&2OQ5EeHPL2X~q-rF#F8h4j$%sGV}Ou=6Evo*~QZe ze!slW*vm25u%I^4uJoPR4ePcjP;Yh~CT$`jMEtw$6%hI#*xfCN4 zILM*W@&`5m?~N?91&b^6s=9TqlJX=8>BSNt?5#~>*rRkR?W4qJwastA$&bh5N00uW z$k6`g5tB!q0*1`FL4kdK(*q-uMl zjMgZ?PzqNWn=8mo5($&-2Inhpn~)qPkw@oEbWjghwnPGkCa;=fUB`E`;Aq-7-5l|& zmO@l*mPi33-9YCpbZ}dQd>x$+z>ytjK;#3?K|6s|zA;j+R%?4BKfiCk|BO@g~^5wQiK*2j;Y=QibY>#}Y z&5k#y-IJ<(&Z9-tzxjfn26uo;t(tJ%bgTrnEeG0*0WhHIEd>ti-c`H5F1YTfs;*fT zx$2hXz>Z>I$3py*z@E={ag96I_i)Ya3)?>Eo;y!<-rYrE_wAaJu-DLlyGugP<2P64 z1Z57`$t@gc-v#H6?Gg}FJ?Pz!4v@GUIfxF>t;(qfasr-Y2WX1~H{KM&3E3NhN*=)h zM|#mYj1GcZ^T``T@bnzZF??|e9D4Gm?+d5T)$n=KfX%gM^@9DR&DC+ccb&ug>VW;Q zYwz9Wbq;Q;X`9Q{b^FFT2e;2EBQ{sv?eHToQg+p?QJ2<;W)O$8^-Bf!+*dgbDA1!) z7qXLR$(usVQfPe9n28|oiI~n#(z7_5oU>Vlf|Q-J`G0;C5Lk-GDtYuoPaz1c#y%?e zYs;EAbOE_T2DV@=X%iE@$;TWO#N#}Pm^1}vpE@Tdz!;YiS6}fDZFyXfi5xWKl016k zV0rr1Wi{kA&I>71g>t?k3X$TsICUI5ivx=10E}Sm6KKUw~6KNg+GydEF}q(wZm%?btY^4LiG-2EAZv zFc_GKnK|N^%#JmNUrW!pYfJ$>8DYN^`vPiaokxnz^b=2{iHR~xb%c`B)Xcn3WKIoz z=4^Ot9rxN%^Cv6H*~9p^5vFSM>;SzszW=hN>FdK?3`UvsgpT0Rfem%TW-&yO^~8mx z1K5qAWZMTve1YTyxpc$wO?l#-XX$0Ftu^70+mI@9g>v+R{5=d5dK5qz_akT>uSgbL zu|qDyFo#AZKZj7|cp}a|EQLkXnCFl#YTm+m4a`uPs9C9R`Ht@w4Q;n(Var0ekLJ>J z%i|2SiB``quG(wR2&EQ`Q1(8_iN#N#SY~e4u=>i_*cA8^pI9`_G=p<9(udisO9+_F zJk+=BV$I)CL5nnuXuRw)y$zJT06t_T-)AGcHcnO$YCz-w5f13T&(4l!bw7+Bzq z8O*!ceq|JuX?kgE6j*t)zcTgED1r#;8)6)pr}9&JJdc%p=t~j2ckc zg|kQN14xA($6{r_NgTnPPS~5_!+zk?xnb0`$}X)%!?wQf0ci@-+u*#XcNBFn zdWEa5+};Of)e#T6TIs>A#W}1ctwn|Vm2XEp`%PPkAZb~BZ#w;Pr{Y6i<@<^t2mTnkc{2<4xgj=HSm? zye|8)^rsROk2en1ACLn43Q$O(tGxQ@o0^d=HK6@E7NCUA$LK7h^9eX|z(^RAfr3<< zl2?O#xh$LpR6TyQ)+L%(!i5jS zrTh;ye5j`Owc%+HxI#8_ZSM5fqp-8W0;%oA0g+SY0{I*LqAubDVXup+KZFR8) z7r^)eep&)da7{k#6SPdekMC%EOoMN=f*KKPR=%3$@?-q)Ma%X@KDdHzYt_W4lV6GdNc9;tKYr)z4T89z>YZBJb!wjx!C^P;;C@4J-iegQAY+Ea4d=k7W;=6 zw}(so2t9U3+Z)RV>9>KAPSZ+L7nsa6?<_%9O>&wI;eX}JKzCWuFvc(u7>&eDhIdQXCL8> zK0kW;gloqt2aci~<9BiR*DsVjh~(CO1&$dJkLtSUFvcmnG)$8**&&2&qdJ<^R{})S zx;3nkh_*PuI%i{@)=lnIcqxkSYx zMRuZaCI1c~E7(V;;Q^$PxBh$5_pg8V`jS_C`}943<9yGL_b>VT-afnH^e#JtMQ5<& zY=Y-+o&U!9Ke%|$>HgNa-#<4Oe(%aVSHAtq$Ic+_LHuPSD!QYgE^;3dmaAW!4Fj)p zv}Z^?)hy9k(G3G-wW2nGlp4bRA#{nsm5E@-DE53v2+%_wa;1^n=`2YqL=tKh1e;m* z8?T?wr$`Nk)o8y5)uaCs8$q2yuGe4WQ$5B)pe$!~m?jz<$_?gdrqw}N(SrJ0m0{^o z?@B)zA8W6{pm92zQGSPsl16mKU~ecBkWx&DDd|!o^G_?k5A`hh9KxW#IO#obRvk8* z?NiSC$nLk&bIg0+)$Al`X$%%OYZPJZpV7X zHk)|!<#i5j>wB*{ZFM&r*5Rj@Y<26ecxr96H;=7za9i(r#b(=q9=v~Eu)A!HtCgHn Wm@6zf8h_#B|M2XpgL5{~&i`+3Y1e`P diff --git a/src/osbridge/backend/__pycache__/__init__.cpython-311.pyc b/src/osbridge/backend/__pycache__/__init__.cpython-311.pyc deleted file mode 100644 index b04283e963d3539a0af3c13cf352cf56f03ae42a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 176 zcmZ3^%ge<81nEh7nW8}YF^B^Lj8MjBHXvgeKn4F!Oml6{npP83g r5+AQuQ2C3)CO1E&G$+-rh!toi$kt+hAn}2jk&*EO1B@tQ28say;}k2p diff --git a/src/osbridge/backend/__pycache__/__init__.cpython-313.pyc b/src/osbridge/backend/__pycache__/__init__.cpython-313.pyc deleted file mode 100644 index d847b511f849e5f9ca31d0184634d1ce95644fef..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 164 zcmey&%ge<81nEh7nW8}YF^B^Lj8MjB79e9PLpp;dqu)w~A|@d3Gf3)|v$Iu9XmM&$ zag2LnQCVh5j7xrbUQT{uN^y*Tu~SiIN_uKcaZz$iesL0nla!d8otl>t6Ca2KczG$)vkyYXeP+cVi4maGb1Bo5i^hl02>l1g8%>k diff --git a/src/osbridge/backend/__pycache__/backend.cpython-311.pyc b/src/osbridge/backend/__pycache__/backend.cpython-311.pyc deleted file mode 100644 index a0a91106d2cd27a0e247aa61480a55aa51b3b37d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4935 zcmb7IOKcm*8Q$efD~e0%Wy^XvT0631+O-hNZXD-Xilk-Q6e*LGABvI<#aYpWsaf%=gTXaMIK?|fJ4qB?}9|L-2c{9(@Gl1%-iHWrN~vNkl`>o~zy?T6#egAn{~wMt$1Ao#aD4 zk;*UdH&dB~bgYon0&5bgR|Snlx^rH^MO;_3VY9nZ$5;~0(eG%@rwG+mh4|%unT>z6 zyr>AWvOHImpH%R2LV7Azr6N+6t?4c+a%ovoR`j_quNbkgey3~e8pHDyv4VNNamp2` zX^Rs8XUZgy=hQz>zPIW7Q{VpSvzxv>W^8{dN`B9%`ri?m?Xlnyn0oN~p>aHT5E42Q zrR5&r$bCSJIms+EwLT*zyjWZl8slzU4yLZifC@m1a4N&e5$GJlK+x+L-tiL*W$GF2 zTtsXy8bx6%r9lPJ$b-DAfoGl#`p@=K0>dsFo^N4(-`gobgAOKG%!2h#$LQGByj`t% z-PSOCa8|)#s|6@YFwKGotrj3Q@cO!X@VPwz-r<&hN4);7*8Fa3_@u+uDM#ChcdRvT z;th255O8|{yb~?`j(B^zTI+FJ!|yn3agO%cR$E^;A7la3)h^R!cdDh2&pXLJ+XVY;P_-*>b-A@A#Ix6f@C)@{aNtS`_I zwuY}cY<=KpJM-*DYaF>U)r1JeSSq z`Pp17p32N=CvEZL<9c8c$@m>dU`RLGFO$Pi3rRIxkwk?j>8W|}6{s^%uR^^B^|w$z zfO;M3hfr^5UVPrsIrTjo_%KcAI$hq%=kC6yUgSsGpRC@hXE=w)EvIB zb@3H?o!FUH*}I(u55ipY?2YYDo40wjD5&MS%0B5VX_`F06?~Nd?_rhAb{0GcpKVTk zy8Wz~0eg?twLhrrMrX-if%4=__OBbS3a_uJvAoI_I@>!4zuP?b`QInrV0H1K`o)^c ziroaqniH3|o^4A+w4}1Ai(KS%b8_Z~^v={9pPKnv4Hq}Pt#HS88E%gucS4v*|8Vm} zE8O9`odpjfqc1<-n%};$v$Xs1!rsRVYVMx;U{wv5yUD}ye7Jr2$5A!^AmaeH%Kk4sNqs)@t-5(Dl^`Gr?v0Z?oU1xO$Ddum%!Qc-(0Inp_ca)MM;k-p9haLfmtlvC~9AotG3;EF0z;lkQo$1a5<} zG6Li|b-=J{cxsn9x5u1Qt-tmecFvSReGXC`lf&KE==9iINPVl-V)+EsMJyXxVj;C& z&HC~y#~=@_*2NMJngq0IxIJ$=2;~TJ66&`mxcT83;k=sQO43>ll3c_}=Tsgj!-8K$ zd`r&$?+EL`KHVmA?^rZ5QI@>f$;+7MwII)%uVG#b@%-=W#j45a;dvyLcwQ!FlsrI4 zoDlL+$SFefRk}-B1wwQ;`UglBO;1o6Ud)tE$Va051;}&z-@fmX>brF4^ZG9yQZ@(pg2{T=!iX&QWWrvK5$|J#MDqxm0LyJ{Q& diff --git a/src/osbridge/backend/__pycache__/backend.cpython-313.pyc b/src/osbridge/backend/__pycache__/backend.cpython-313.pyc deleted file mode 100644 index 43637db051fa1c8969be399d27d65160c7ef22de..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4478 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 Te|W2Pb+ipW7rx=ax`qD*jir)L diff --git a/src/osbridge/backend/__pycache__/common.cpython-313.pyc b/src/osbridge/backend/__pycache__/common.cpython-313.pyc deleted file mode 100644 index 6eb6a54485f83216313f269deda98570418620ab..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10362 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 O$^Yu>=^ABE>i+^d@I7+? diff --git a/src/osbridge/resources/__pycache__/resources_rc.cpython-311.pyc b/src/osbridge/resources/__pycache__/resources_rc.cpython-311.pyc deleted file mode 100644 index 774b3dc8b3f987aca8d6b0333c45339ff013f7ae..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14681 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 diff --git a/src/osbridge/resources/__pycache__/resources_rc.cpython-313.pyc b/src/osbridge/resources/__pycache__/resources_rc.cpython-313.pyc deleted file mode 100644 index 3c9ea86d856c9628beaf791cfec64e79bcf2e951..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14594 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 PzVM^Cmb9V75OMw&KeLEb From 3eda1ce3c480361bc73237940eed233dee77e70c Mon Sep 17 00:00:00 2001 From: mhsuhail00 Date: Tue, 9 Dec 2025 21:27:52 +0530 Subject: [PATCH 44/59] Fetched osbridge work with commit history --- {legacy_src => src/osdagbridge/desktop/legacy_src}/.gitignore | 0 {legacy_src => src/osdagbridge/desktop/legacy_src}/README.md | 0 .../osdagbridge/desktop/legacy_src}/requirements.txt | 0 .../desktop/legacy_src}/src/osbridge/backend/__init__.py | 0 .../desktop/legacy_src}/src/osbridge/backend/backend.py | 0 .../desktop/legacy_src}/src/osbridge/backend/common.py | 0 .../desktop/legacy_src}/src/osbridge/resources/resources.qrc | 0 .../desktop/legacy_src}/src/osbridge/resources/resources_rc.py | 0 .../legacy_src}/src/osbridge/resources/themes/darkstyle.qss | 0 .../legacy_src}/src/osbridge/resources/themes/lightstyle.qss | 0 .../src/osbridge/resources/vectors/arrow_down_dark.svg | 0 .../src/osbridge/resources/vectors/arrow_down_light.svg | 0 .../legacy_src}/src/osbridge/resources/vectors/arrow_up_dark.svg | 0 .../legacy_src}/src/osbridge/resources/vectors/arrow_up_light.svg | 0 .../desktop/legacy_src}/src/osbridge/resources/vectors/design.svg | 0 .../legacy_src}/src/osbridge/resources/vectors/design_report.svg | 0 .../legacy_src}/src/osbridge/resources/vectors/lock_close.svg | 0 .../legacy_src}/src/osbridge/resources/vectors/lock_open.svg | 0 .../desktop/legacy_src}/src/osbridge/resources/vectors/save.svg | 0 .../osdagbridge/desktop/legacy_src}/src/osbridge/template_page.py | 0 .../osdagbridge/desktop/legacy_src}/src/osbridge/ui/__init__.py | 0 .../desktop/legacy_src}/src/osbridge/ui/additional_inputs.py | 0 .../desktop/legacy_src}/src/osbridge/ui/custom_buttons.py | 0 .../osdagbridge/desktop/legacy_src}/src/osbridge/ui/input_dock.py | 0 .../desktop/legacy_src}/src/osbridge/ui/output_dock.py | 0 25 files changed, 0 insertions(+), 0 deletions(-) rename {legacy_src => src/osdagbridge/desktop/legacy_src}/.gitignore (100%) rename {legacy_src => src/osdagbridge/desktop/legacy_src}/README.md (100%) rename {legacy_src => src/osdagbridge/desktop/legacy_src}/requirements.txt (100%) rename {legacy_src => src/osdagbridge/desktop/legacy_src}/src/osbridge/backend/__init__.py (100%) rename {legacy_src => src/osdagbridge/desktop/legacy_src}/src/osbridge/backend/backend.py (100%) rename {legacy_src => src/osdagbridge/desktop/legacy_src}/src/osbridge/backend/common.py (100%) rename {legacy_src => src/osdagbridge/desktop/legacy_src}/src/osbridge/resources/resources.qrc (100%) rename {legacy_src => src/osdagbridge/desktop/legacy_src}/src/osbridge/resources/resources_rc.py (100%) rename {legacy_src => src/osdagbridge/desktop/legacy_src}/src/osbridge/resources/themes/darkstyle.qss (100%) rename {legacy_src => src/osdagbridge/desktop/legacy_src}/src/osbridge/resources/themes/lightstyle.qss (100%) rename {legacy_src => src/osdagbridge/desktop/legacy_src}/src/osbridge/resources/vectors/arrow_down_dark.svg (100%) rename {legacy_src => src/osdagbridge/desktop/legacy_src}/src/osbridge/resources/vectors/arrow_down_light.svg (100%) rename {legacy_src => src/osdagbridge/desktop/legacy_src}/src/osbridge/resources/vectors/arrow_up_dark.svg (100%) rename {legacy_src => src/osdagbridge/desktop/legacy_src}/src/osbridge/resources/vectors/arrow_up_light.svg (100%) rename {legacy_src => src/osdagbridge/desktop/legacy_src}/src/osbridge/resources/vectors/design.svg (100%) rename {legacy_src => src/osdagbridge/desktop/legacy_src}/src/osbridge/resources/vectors/design_report.svg (100%) rename {legacy_src => src/osdagbridge/desktop/legacy_src}/src/osbridge/resources/vectors/lock_close.svg (100%) rename {legacy_src => src/osdagbridge/desktop/legacy_src}/src/osbridge/resources/vectors/lock_open.svg (100%) rename {legacy_src => src/osdagbridge/desktop/legacy_src}/src/osbridge/resources/vectors/save.svg (100%) rename {legacy_src => src/osdagbridge/desktop/legacy_src}/src/osbridge/template_page.py (100%) rename {legacy_src => src/osdagbridge/desktop/legacy_src}/src/osbridge/ui/__init__.py (100%) rename {legacy_src => src/osdagbridge/desktop/legacy_src}/src/osbridge/ui/additional_inputs.py (100%) rename {legacy_src => src/osdagbridge/desktop/legacy_src}/src/osbridge/ui/custom_buttons.py (100%) rename {legacy_src => src/osdagbridge/desktop/legacy_src}/src/osbridge/ui/input_dock.py (100%) rename {legacy_src => src/osdagbridge/desktop/legacy_src}/src/osbridge/ui/output_dock.py (100%) diff --git a/legacy_src/.gitignore b/src/osdagbridge/desktop/legacy_src/.gitignore similarity index 100% rename from legacy_src/.gitignore rename to src/osdagbridge/desktop/legacy_src/.gitignore diff --git a/legacy_src/README.md b/src/osdagbridge/desktop/legacy_src/README.md similarity index 100% rename from legacy_src/README.md rename to src/osdagbridge/desktop/legacy_src/README.md diff --git a/legacy_src/requirements.txt b/src/osdagbridge/desktop/legacy_src/requirements.txt similarity index 100% rename from legacy_src/requirements.txt rename to src/osdagbridge/desktop/legacy_src/requirements.txt diff --git a/legacy_src/src/osbridge/backend/__init__.py b/src/osdagbridge/desktop/legacy_src/src/osbridge/backend/__init__.py similarity index 100% rename from legacy_src/src/osbridge/backend/__init__.py rename to src/osdagbridge/desktop/legacy_src/src/osbridge/backend/__init__.py diff --git a/legacy_src/src/osbridge/backend/backend.py b/src/osdagbridge/desktop/legacy_src/src/osbridge/backend/backend.py similarity index 100% rename from legacy_src/src/osbridge/backend/backend.py rename to src/osdagbridge/desktop/legacy_src/src/osbridge/backend/backend.py diff --git a/legacy_src/src/osbridge/backend/common.py b/src/osdagbridge/desktop/legacy_src/src/osbridge/backend/common.py similarity index 100% rename from legacy_src/src/osbridge/backend/common.py rename to src/osdagbridge/desktop/legacy_src/src/osbridge/backend/common.py diff --git a/legacy_src/src/osbridge/resources/resources.qrc b/src/osdagbridge/desktop/legacy_src/src/osbridge/resources/resources.qrc similarity index 100% rename from legacy_src/src/osbridge/resources/resources.qrc rename to src/osdagbridge/desktop/legacy_src/src/osbridge/resources/resources.qrc diff --git a/legacy_src/src/osbridge/resources/resources_rc.py b/src/osdagbridge/desktop/legacy_src/src/osbridge/resources/resources_rc.py similarity index 100% rename from legacy_src/src/osbridge/resources/resources_rc.py rename to src/osdagbridge/desktop/legacy_src/src/osbridge/resources/resources_rc.py diff --git a/legacy_src/src/osbridge/resources/themes/darkstyle.qss b/src/osdagbridge/desktop/legacy_src/src/osbridge/resources/themes/darkstyle.qss similarity index 100% rename from legacy_src/src/osbridge/resources/themes/darkstyle.qss rename to src/osdagbridge/desktop/legacy_src/src/osbridge/resources/themes/darkstyle.qss diff --git a/legacy_src/src/osbridge/resources/themes/lightstyle.qss b/src/osdagbridge/desktop/legacy_src/src/osbridge/resources/themes/lightstyle.qss similarity index 100% rename from legacy_src/src/osbridge/resources/themes/lightstyle.qss rename to src/osdagbridge/desktop/legacy_src/src/osbridge/resources/themes/lightstyle.qss diff --git a/legacy_src/src/osbridge/resources/vectors/arrow_down_dark.svg b/src/osdagbridge/desktop/legacy_src/src/osbridge/resources/vectors/arrow_down_dark.svg similarity index 100% rename from legacy_src/src/osbridge/resources/vectors/arrow_down_dark.svg rename to src/osdagbridge/desktop/legacy_src/src/osbridge/resources/vectors/arrow_down_dark.svg diff --git a/legacy_src/src/osbridge/resources/vectors/arrow_down_light.svg b/src/osdagbridge/desktop/legacy_src/src/osbridge/resources/vectors/arrow_down_light.svg similarity index 100% rename from legacy_src/src/osbridge/resources/vectors/arrow_down_light.svg rename to src/osdagbridge/desktop/legacy_src/src/osbridge/resources/vectors/arrow_down_light.svg diff --git a/legacy_src/src/osbridge/resources/vectors/arrow_up_dark.svg b/src/osdagbridge/desktop/legacy_src/src/osbridge/resources/vectors/arrow_up_dark.svg similarity index 100% rename from legacy_src/src/osbridge/resources/vectors/arrow_up_dark.svg rename to src/osdagbridge/desktop/legacy_src/src/osbridge/resources/vectors/arrow_up_dark.svg diff --git a/legacy_src/src/osbridge/resources/vectors/arrow_up_light.svg b/src/osdagbridge/desktop/legacy_src/src/osbridge/resources/vectors/arrow_up_light.svg similarity index 100% rename from legacy_src/src/osbridge/resources/vectors/arrow_up_light.svg rename to src/osdagbridge/desktop/legacy_src/src/osbridge/resources/vectors/arrow_up_light.svg diff --git a/legacy_src/src/osbridge/resources/vectors/design.svg b/src/osdagbridge/desktop/legacy_src/src/osbridge/resources/vectors/design.svg similarity index 100% rename from legacy_src/src/osbridge/resources/vectors/design.svg rename to src/osdagbridge/desktop/legacy_src/src/osbridge/resources/vectors/design.svg diff --git a/legacy_src/src/osbridge/resources/vectors/design_report.svg b/src/osdagbridge/desktop/legacy_src/src/osbridge/resources/vectors/design_report.svg similarity index 100% rename from legacy_src/src/osbridge/resources/vectors/design_report.svg rename to src/osdagbridge/desktop/legacy_src/src/osbridge/resources/vectors/design_report.svg diff --git a/legacy_src/src/osbridge/resources/vectors/lock_close.svg b/src/osdagbridge/desktop/legacy_src/src/osbridge/resources/vectors/lock_close.svg similarity index 100% rename from legacy_src/src/osbridge/resources/vectors/lock_close.svg rename to src/osdagbridge/desktop/legacy_src/src/osbridge/resources/vectors/lock_close.svg diff --git a/legacy_src/src/osbridge/resources/vectors/lock_open.svg b/src/osdagbridge/desktop/legacy_src/src/osbridge/resources/vectors/lock_open.svg similarity index 100% rename from legacy_src/src/osbridge/resources/vectors/lock_open.svg rename to src/osdagbridge/desktop/legacy_src/src/osbridge/resources/vectors/lock_open.svg diff --git a/legacy_src/src/osbridge/resources/vectors/save.svg b/src/osdagbridge/desktop/legacy_src/src/osbridge/resources/vectors/save.svg similarity index 100% rename from legacy_src/src/osbridge/resources/vectors/save.svg rename to src/osdagbridge/desktop/legacy_src/src/osbridge/resources/vectors/save.svg diff --git a/legacy_src/src/osbridge/template_page.py b/src/osdagbridge/desktop/legacy_src/src/osbridge/template_page.py similarity index 100% rename from legacy_src/src/osbridge/template_page.py rename to src/osdagbridge/desktop/legacy_src/src/osbridge/template_page.py diff --git a/legacy_src/src/osbridge/ui/__init__.py b/src/osdagbridge/desktop/legacy_src/src/osbridge/ui/__init__.py similarity index 100% rename from legacy_src/src/osbridge/ui/__init__.py rename to src/osdagbridge/desktop/legacy_src/src/osbridge/ui/__init__.py diff --git a/legacy_src/src/osbridge/ui/additional_inputs.py b/src/osdagbridge/desktop/legacy_src/src/osbridge/ui/additional_inputs.py similarity index 100% rename from legacy_src/src/osbridge/ui/additional_inputs.py rename to src/osdagbridge/desktop/legacy_src/src/osbridge/ui/additional_inputs.py diff --git a/legacy_src/src/osbridge/ui/custom_buttons.py b/src/osdagbridge/desktop/legacy_src/src/osbridge/ui/custom_buttons.py similarity index 100% rename from legacy_src/src/osbridge/ui/custom_buttons.py rename to src/osdagbridge/desktop/legacy_src/src/osbridge/ui/custom_buttons.py diff --git a/legacy_src/src/osbridge/ui/input_dock.py b/src/osdagbridge/desktop/legacy_src/src/osbridge/ui/input_dock.py similarity index 100% rename from legacy_src/src/osbridge/ui/input_dock.py rename to src/osdagbridge/desktop/legacy_src/src/osbridge/ui/input_dock.py diff --git a/legacy_src/src/osbridge/ui/output_dock.py b/src/osdagbridge/desktop/legacy_src/src/osbridge/ui/output_dock.py similarity index 100% rename from legacy_src/src/osbridge/ui/output_dock.py rename to src/osdagbridge/desktop/legacy_src/src/osbridge/ui/output_dock.py From eb3851b0f3e3516f8ee60bc0162269c42a60d45a Mon Sep 17 00:00:00 2001 From: mhsuhail00 Date: Fri, 12 Dec 2025 16:57:41 +0530 Subject: [PATCH 45/59] Restructured the older work --- .gitignore | 4 +- .../requirements.txt => requirements.txt | 0 src/osdagbridge/__init__.py | 2 - src/osdagbridge/core/__init__.py | 2 - .../bridge_types/plate_girder/ui_fields.py} | 25 +- src/osdagbridge/core/utils/common.py | 241 + src/osdagbridge/desktop/__init__.py | 1 - src/osdagbridge/desktop/__main__.py | 36 + src/osdagbridge/desktop/legacy_src/.gitignore | 2 - src/osdagbridge/desktop/legacy_src/README.md | 55 - .../src/osbridge/backend/__init__.py | 2 - .../legacy_src/src/osbridge/backend/common.py | 406 -- .../src/osbridge/resources/resources_rc.py | 941 ---- .../osbridge/resources/themes/darkstyle.qss | 1646 ------- .../osbridge/resources/themes/lightstyle.qss | 1607 ------- .../legacy_src/src/osbridge/template_page.py | 480 -- .../legacy_src/src/osbridge/ui/__init__.py | 2 - .../src/osbridge/ui/additional_inputs.py | 1996 -------- src/osdagbridge/desktop/main.py | 3 - .../src/osbridge => }/resources/resources.qrc | 9 + .../desktop/resources/resources_rc.py | 651 +++ .../desktop/resources/themes/darkstyle.qss | 32 + .../desktop/resources/themes/lightstyle.qss | 48 + .../resources/vectors/arrow_down_dark.svg | 0 .../resources/vectors/arrow_down_light.svg | 0 .../resources/vectors/arrow_up_dark.svg | 0 .../resources/vectors/arrow_up_light.svg | 0 .../desktop/resources/vectors/checked.svg | 1 + .../resources/vectors/design.svg | 0 .../resources/vectors/design_report.svg | 0 .../resources/vectors/lock_close.svg | 0 .../resources/vectors/lock_open.svg | 0 .../osbridge => }/resources/vectors/save.svg | 0 src/osdagbridge/desktop/ui/__init__.py | 1 - .../desktop/ui/additional_inputs.py | 4017 +++++++++++++++++ .../osbridge/ui => ui/docks}/input_dock.py | 877 +++- src/osdagbridge/desktop/ui/docks/log_dock.py | 91 + .../osbridge/ui => ui/docks}/output_dock.py | 126 +- src/osdagbridge/desktop/ui/main_window.ui | 2 - src/osdagbridge/desktop/ui/template_page.py | 128 + .../ui => ui/utils}/custom_buttons.py | 0 41 files changed, 6099 insertions(+), 7335 deletions(-) rename src/osdagbridge/desktop/legacy_src/requirements.txt => requirements.txt (100%) delete mode 100644 src/osdagbridge/__init__.py delete mode 100644 src/osdagbridge/core/__init__.py rename src/osdagbridge/{desktop/legacy_src/src/osbridge/backend/backend.py => core/bridge_types/plate_girder/ui_fields.py} (82%) create mode 100644 src/osdagbridge/core/utils/common.py delete mode 100644 src/osdagbridge/desktop/__init__.py create mode 100644 src/osdagbridge/desktop/__main__.py delete mode 100644 src/osdagbridge/desktop/legacy_src/.gitignore delete mode 100644 src/osdagbridge/desktop/legacy_src/README.md delete mode 100644 src/osdagbridge/desktop/legacy_src/src/osbridge/backend/__init__.py delete mode 100644 src/osdagbridge/desktop/legacy_src/src/osbridge/backend/common.py delete mode 100644 src/osdagbridge/desktop/legacy_src/src/osbridge/resources/resources_rc.py delete mode 100644 src/osdagbridge/desktop/legacy_src/src/osbridge/resources/themes/darkstyle.qss delete mode 100644 src/osdagbridge/desktop/legacy_src/src/osbridge/resources/themes/lightstyle.qss delete mode 100644 src/osdagbridge/desktop/legacy_src/src/osbridge/template_page.py delete mode 100644 src/osdagbridge/desktop/legacy_src/src/osbridge/ui/__init__.py delete mode 100644 src/osdagbridge/desktop/legacy_src/src/osbridge/ui/additional_inputs.py delete mode 100644 src/osdagbridge/desktop/main.py rename src/osdagbridge/desktop/{legacy_src/src/osbridge => }/resources/resources.qrc (59%) create mode 100644 src/osdagbridge/desktop/resources/resources_rc.py create mode 100644 src/osdagbridge/desktop/resources/themes/darkstyle.qss create mode 100644 src/osdagbridge/desktop/resources/themes/lightstyle.qss rename src/osdagbridge/desktop/{legacy_src/src/osbridge => }/resources/vectors/arrow_down_dark.svg (100%) rename src/osdagbridge/desktop/{legacy_src/src/osbridge => }/resources/vectors/arrow_down_light.svg (100%) rename src/osdagbridge/desktop/{legacy_src/src/osbridge => }/resources/vectors/arrow_up_dark.svg (100%) rename src/osdagbridge/desktop/{legacy_src/src/osbridge => }/resources/vectors/arrow_up_light.svg (100%) create mode 100644 src/osdagbridge/desktop/resources/vectors/checked.svg rename src/osdagbridge/desktop/{legacy_src/src/osbridge => }/resources/vectors/design.svg (100%) rename src/osdagbridge/desktop/{legacy_src/src/osbridge => }/resources/vectors/design_report.svg (100%) rename src/osdagbridge/desktop/{legacy_src/src/osbridge => }/resources/vectors/lock_close.svg (100%) rename src/osdagbridge/desktop/{legacy_src/src/osbridge => }/resources/vectors/lock_open.svg (100%) rename src/osdagbridge/desktop/{legacy_src/src/osbridge => }/resources/vectors/save.svg (100%) delete mode 100644 src/osdagbridge/desktop/ui/__init__.py create mode 100644 src/osdagbridge/desktop/ui/additional_inputs.py rename src/osdagbridge/desktop/{legacy_src/src/osbridge/ui => ui/docks}/input_dock.py (58%) create mode 100644 src/osdagbridge/desktop/ui/docks/log_dock.py rename src/osdagbridge/desktop/{legacy_src/src/osbridge/ui => ui/docks}/output_dock.py (79%) delete mode 100644 src/osdagbridge/desktop/ui/main_window.ui create mode 100644 src/osdagbridge/desktop/ui/template_page.py rename src/osdagbridge/desktop/{legacy_src/src/osbridge/ui => ui/utils}/custom_buttons.py (100%) diff --git a/.gitignore b/.gitignore index b7faf403..c1085940 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ # Byte-compiled / optimized / DLL files -__pycache__/ +*/__pycache__/ *.py[codz] *$py.class @@ -125,7 +125,7 @@ ipython_config.py .pixi # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm -__pypackages__/ +*/__pypackages__/ # Celery stuff celerybeat-schedule diff --git a/src/osdagbridge/desktop/legacy_src/requirements.txt b/requirements.txt similarity index 100% rename from src/osdagbridge/desktop/legacy_src/requirements.txt rename to requirements.txt diff --git a/src/osdagbridge/__init__.py b/src/osdagbridge/__init__.py deleted file mode 100644 index a4933da9..00000000 --- a/src/osdagbridge/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -"""osdagbridge package.""" -__version__ = "0.0.0" diff --git a/src/osdagbridge/core/__init__.py b/src/osdagbridge/core/__init__.py deleted file mode 100644 index 62df716e..00000000 --- a/src/osdagbridge/core/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -"""Core package for OsdagBridge""" -from .models import * diff --git a/src/osdagbridge/desktop/legacy_src/src/osbridge/backend/backend.py b/src/osdagbridge/core/bridge_types/plate_girder/ui_fields.py similarity index 82% rename from src/osdagbridge/desktop/legacy_src/src/osbridge/backend/backend.py rename to src/osdagbridge/core/bridge_types/plate_girder/ui_fields.py index b8d6975d..a6f0bb9c 100644 --- a/src/osdagbridge/desktop/legacy_src/src/osbridge/backend/backend.py +++ b/src/osdagbridge/core/bridge_types/plate_girder/ui_fields.py @@ -1,15 +1,12 @@ -from .common import * +from osdagbridge.core.utils.common import * -class BackendOsBridge: +class FrontendData: """Backend for Highway Bridge Design""" def __init__(self): self.module = KEY_DISP_FINPLATE self.design_status = False self.design_button_status = False - - def module_name(self): - return KEY_DISP_FINPLATE def input_values(self): """Return list of input fields for the UI""" @@ -63,27 +60,15 @@ def input_values(self): return options_list - def customized_input(self): - """Return empty list for now""" - return [] - - def input_value_changed(self): - """Return None - no dynamic changes needed""" - return None - def set_osdaglogger(self, key): - """Mock logger setup""" + """Logger setup""" print("Logger set up (mock)") def output_values(self, flag): - """Mock output values""" + """output values List""" return [] def func_for_validation(self, design_inputs): - """Mock validation - always passes for testing""" + """Validation Function""" return None - - def get_3d_components(self): - """Mock 3D components""" - return [] diff --git a/src/osdagbridge/core/utils/common.py b/src/osdagbridge/core/utils/common.py new file mode 100644 index 00000000..2f407e2a --- /dev/null +++ b/src/osdagbridge/core/utils/common.py @@ -0,0 +1,241 @@ +# Constants for input types +TYPE_MODULE = "module" +TYPE_TITLE = "title" +TYPE_COMBOBOX = "combobox" +TYPE_COMBOBOX_CUSTOMIZED = "combobox_customized" +TYPE_TEXTBOX = "textbox" +TYPE_IMAGE = "image" + +# Keys for inputs +KEY_MODULE = "Module" +KEY_STRUCTURE_TYPE = "Structure Type" +KEY_PROJECT_LOCATION = "Project Location" +KEY_SPAN = "Span" +KEY_CARRIAGEWAY_WIDTH = "Carriageway Width" +KEY_INCLUDE_MEDIAN = "Include Median" +KEY_FOOTPATH = "Footpath" +KEY_SKEW_ANGLE = "Skew Angle" +KEY_GIRDER = "Girder" +KEY_CROSS_BRACING = "Cross Bracing" +KEY_END_DIAPHRAGM = "End Diaphragm" +KEY_DECK = "Deck" +KEY_DECK_CONCRETE_GRADE_BASIC = "Deck Concrete Grade" + +# Display names +KEY_DISP_FINPLATE = "Highway Bridge Design" +DISP_TITLE_STRUCTURE = "Type of Structure" +KEY_DISP_STRUCTURE_TYPE = "Structure Type" +DISP_TITLE_PROJECT = "Project Location" +KEY_DISP_PROJECT_LOCATION = "City in India*" +DISP_TITLE_GEOMETRIC = "Geometric Details" +KEY_DISP_SPAN = "Span (m)* [20-45]" +KEY_DISP_CARRIAGEWAY_WIDTH = "Carriageway Width (m)* [≥4.25]" +KEY_DISP_FOOTPATH = "Footpath" +KEY_DISP_SKEW_ANGLE = "Skew Angle (degrees) [±15]" +DISP_TITLE_MATERIAL = "Material Inputs" +KEY_DISP_GIRDER = "Girder" +KEY_DISP_CROSS_BRACING = "Cross Bracing" +KEY_DISP_END_DIAPHRAGM = "End Diaphragm" +KEY_DISP_DECK = "Deck" +KEY_DISP_DECK_CONCRETE_GRADE = "Deck Concrete Grade [M25+]" + +# Sample values +# Type of Structure: Defines the application of the steel girder bridge +# Currently only covers highway bridge +VALUES_STRUCTURE_TYPE = ["Highway Bridge", "Other"] + +# Project Location: Cities in India for load calculations +# Organized by regions for easier navigation +VALUES_PROJECT_LOCATION = [ + "Delhi", "Mumbai", "Bangalore", "Kolkata", "Chennai", "Hyderabad", + "Ahmedabad", "Pune", "Surat", "Jaipur", "Lucknow", "Kanpur", + "Nagpur", "Indore", "Thane", "Bhopal", "Visakhapatnam", "Pimpri-Chinchwad", + "Patna", "Vadodara", "Ghaziabad", "Ludhiana", "Agra", "Nashik", + "Faridabad", "Meerut", "Rajkot", "Kalyan-Dombivali", "Vasai-Virar", "Varanasi", + "Srinagar", "Aurangabad", "Dhanbad", "Amritsar", "Navi Mumbai", "Allahabad", + "Ranchi", "Howrah", "Coimbatore", "Jabalpur", "Gwalior", "Vijayawada", + "Jodhpur", "Madurai", "Raipur", "Kota", "Chandigarh", "Guwahati", + "Custom" # Allow custom location entry +] + +# Footpath: Single sided or none or both +# Default: None +# Note: IRC 5 Clause 101.41 requires safety kerb when footpath is not present +VALUES_FOOTPATH = ["None", "Single Sided", "Both"] + +VALUES_MATERIAL = [ + "E 250A", "E 250BR", "E 250B0", "E 250C", + "E 275A", "E 275BR", "E 275B0", "E 275C", + "E 300A", "E 300BR", "E 300B0", "E 300C", + "E 350A", "E 350BR", "E 350B0", "E 350C", + "E 410A", "E 410BR", "E 410B0", "E 410C", + "E 450A", "E 450BR", + "E 550A", "E 550BR", + "E 600A", "E 600BR", + "E 650A", "E 650BR" +] + +# Validation limits +# Span: Between 20 to 45 meters +SPAN_MIN = 20.0 +SPAN_MAX = 45.0 + +# Carriageway Width limits per IRC 5 Clause 104.3.1 +CARRIAGEWAY_WIDTH_MIN = 4.25 # No median present +CARRIAGEWAY_WIDTH_MIN_WITH_MEDIAN = 7.5 # Each carriageway when median provided +CARRIAGEWAY_WIDTH_MAX_LIMIT = 23.6 # Current software cap (subject to change) + +# Skew Angle: IRC 24 (2010) requires detailed analysis when skew angle exceeds ±15 degrees +# Default: 0 degrees +SKEW_ANGLE_MIN = -15.0 +SKEW_ANGLE_MAX = 15.0 +SKEW_ANGLE_DEFAULT = 0.0 + +# ===== Additional Inputs Constants ===== + +# Typical Section Details Keys +KEY_GIRDER_SPACING = "Girder Spacing" +KEY_DECK_OVERHANG = "Deck Overhang Width" +KEY_NO_OF_GIRDERS = "No. of Girders" +KEY_DECK_THICKNESS = "Deck Thickness" +KEY_DECK_CONCRETE_GRADE = "Deck Concrete Grade" +KEY_DECK_REINF_MATERIAL = "Deck Reinforcement Material" +KEY_DECK_REINF_SIZE = "Deck Reinforcement Size" +KEY_DECK_REINF_SPACING_LONG = "Deck Reinforcement Spacing Longitudinal" +KEY_DECK_REINF_SPACING_TRANS = "Deck Reinforcement Spacing Transverse" +KEY_FOOTPATH_WIDTH = "Footpath Width" +KEY_FOOTPATH_THICKNESS = "Footpath Thickness" +KEY_RAILING_PRESENT = "Railing Present" +KEY_RAILING_WIDTH = "Railing Width" +KEY_RAILING_HEIGHT = "Railing Height" +KEY_SAFETY_KERB_PRESENT = "Safety Kerb Present" +KEY_SAFETY_KERB_WIDTH = "Safety Kerb Width" +KEY_SAFETY_KERB_THICKNESS = "Safety Kerb Thickness" +KEY_CRASH_BARRIER_PRESENT = "Crash Barrier Present" +KEY_CRASH_BARRIER_TYPE = "Crash Barrier Type" +KEY_CRASH_BARRIER_DENSITY = "Crash Barrier Material Density" +KEY_CRASH_BARRIER_WIDTH = "Crash Barrier Width" +KEY_CRASH_BARRIER_AREA = "Crash Barrier Area" + +# Section Properties Keys +KEY_GIRDER_TYPE = "Girder Type" +KEY_GIRDER_IS_SECTION = "Girder IS Section" +KEY_GIRDER_SYMMETRY = "Girder Symmetry" +KEY_GIRDER_TOP_FLANGE_WIDTH = "Girder Top Flange Width" +KEY_GIRDER_TOP_FLANGE_THICKNESS = "Girder Top Flange Thickness" +KEY_GIRDER_BOTTOM_FLANGE_WIDTH = "Girder Bottom Flange Width" +KEY_GIRDER_BOTTOM_FLANGE_THICKNESS = "Girder Bottom Flange Thickness" +KEY_GIRDER_DEPTH = "Girder Depth" +KEY_GIRDER_WEB_THICKNESS = "Girder Web Thickness" +KEY_GIRDER_TORSIONAL_RESTRAINT = "Torsional Restraint" +KEY_GIRDER_WARPING_RESTRAINT = "Warping Restraint" +KEY_GIRDER_WEB_TYPE = "Web Type" + +KEY_STIFFENER_DESIGN_METHOD = "Stiffener Design Method" +KEY_STIFFENER_PLATE_THICKNESS = "Stiffener Plate Thickness" +KEY_STIFFENER_SPACING = "Stiffener Spacing" +KEY_LONGITUDINAL_STIFFENER = "Longitudinal Stiffener" +KEY_LONGITUDINAL_STIFFENER_THICKNESS = "Longitudinal Stiffener Thickness" + +KEY_CROSS_BRACING_TYPE = "Cross Bracing Type" +KEY_CROSS_BRACING_SECTION = "Cross Bracing Section" +KEY_BRACKET_SECTION = "Bracket Section" +KEY_CROSS_BRACING_SPACING = "Cross Bracing Spacing" + +KEY_END_DIAPHRAGM_TYPE = "End Diaphragm Type" +KEY_END_DIAPHRAGM_SECTION = "End Diaphragm Section" +KEY_END_DIAPHRAGM_SPACING = "End Diaphragm Spacing" + +# Dead Load Keys +KEY_SELF_WEIGHT = "Self Weight" +KEY_SELF_WEIGHT_FACTOR = "Self Weight Factor" +KEY_WEARING_COAT_MATERIAL = "Wearing Coat Material" +KEY_WEARING_COAT_DENSITY = "Wearing Coat Density" +KEY_WEARING_COAT_THICKNESS = "Wearing Coat Thickness" +KEY_RAILING_LOAD_COUNT = "No. of Railings" +KEY_RAILING_LOAD = "Railing Load" +KEY_RAILING_LOAD_LOCATION = "Railing Load Location" +KEY_CRASH_BARRIER_LOAD_COUNT = "No. of Crash Barriers" +KEY_CRASH_BARRIER_LOAD = "Crash Barrier Load" +KEY_CRASH_BARRIER_LOAD_LOCATION = "Crash Barrier Load Location" + +# Live Load Keys +KEY_IRC_CLASS_A = "IRC Class A" +KEY_IRC_CLASS_70R = "IRC Class 70R" +KEY_IRC_CLASS_AA = "IRC Class AA" +KEY_IRC_CLASS_SV = "IRC Class SV" +KEY_CUSTOM_VEHICLE = "Custom Vehicle" +KEY_CUSTOM_AXLE_TYPE = "Custom Axle Type" +KEY_CUSTOM_NO_AXLES = "Custom Number of Axles" +KEY_CUSTOM_AXLE_LOAD = "Custom Axle Load" +KEY_CUSTOM_AXLE_SPACING = "Custom Axle Spacing" +KEY_CUSTOM_VEHICLE_SPACING = "Custom Vehicle Spacing" +KEY_CUSTOM_ECCENTRICITY = "Custom Eccentricity" +KEY_FOOTPATH_PRESSURE = "Footpath Pressure" +KEY_FOOTPATH_PRESSURE_VALUE = "Footpath Pressure Value" + +# Support Condition Keys +KEY_LEFT_SUPPORT = "Left Support" +KEY_RIGHT_SUPPORT = "Right Support" +KEY_BEARING_LENGTH = "Bearing Length" + +# Value Lists for Additional Inputs +VALUES_YES_NO = ["No", "Yes"] +VALUES_DECK_CONCRETE_GRADE = [ + "M 25", "M 30", "M 35", "M 40", "M 45", "M 50", + "M 55", "M 60", "M 65", "M 70", "M 75", "M 80", + "M 85", "M 90" +] +VALUES_REINF_MATERIAL = ["Fe 415", "Fe 500", "Fe 550"] +VALUES_REINF_SIZE = ["8", "10", "12", "16", "20", "25", "32"] +VALUES_CRASH_BARRIER_TYPE = [ + "IRC 5 - RCC Crash Barrier", + "IRC 5 - Steel Crash Barrier", + "IRC 5 - Metal Beam", + "Custom" +] +VALUES_MEDIAN_TYPE = [ + "IRC 5 - Raised Kerb", + "IRC 5 - Flush Median", + "Custom" +] +VALUES_GIRDER_TYPE = ["IS Standard Rolled Beam", "Plate Girder"] +VALUES_GIRDER_SYMMETRY = ["Symmetrical", "Unsymmetrical"] +VALUES_OPTIMIZATION_MODE = ["Optimized", "Customized", "All"] +VALUES_TORSIONAL_RESTRAINT = ["Fully Restrained", "Partially Restrained - Support Connect", "Partially Restrained - Bearing Support"] +VALUES_WARPING_RESTRAINT = ["Both Flange Restraint", "No Restraint"] +VALUES_WEB_TYPE = ["Thin Web", "Thick Web"] +VALUES_STIFFENER_DESIGN = ["Simple Post", "Tension Field"] +VALUES_CROSS_BRACING_TYPE = ["K-bracing", "K-bracing with top bracket", "X-bracing", "X-bracing with bottom bracket", "X-bracing with top and bottom brackets"] +VALUES_END_DIAPHRAGM_TYPE = ["Cross Bracing", "Rolled Beam", "Welded Beam"] +VALUES_WEARING_COAT_MATERIAL = ["Concrete", "Bituminous", "Other"] +VALUES_RAILING_TYPE = [ + "IRC 5 - RCC Railing", + "IRC 5 - Steel Railing", + "Custom" +] +VALUES_CUSTOM_AXLE_TYPE = ["Single", "Bogie"] +VALUES_FOOTPATH_PRESSURE_MODE = ["Automatic", "User-defined"] +VALUES_SUPPORT_TYPE = ["Fixed", "Pinned"] + +# Default values +DEFAULT_SELF_WEIGHT_FACTOR = 1.0 +DEFAULT_CONCRETE_DENSITY = 25.0 # kN/m³ +DEFAULT_STEEL_DENSITY = 78.5 # kN/m³ +DEFAULT_BEARING_LENGTH = 0.0 # mm + +# Typical Section Details Validation Constants (IRC 5) +MIN_FOOTPATH_WIDTH = 1.5 # meters (IRC 5 Clause 104.3.6) +MIN_RAILING_HEIGHT = 1.0 # meters (IRC 5 Clauses 109.7.2.3 & 109.7.2.4) +MIN_SAFETY_KERB_WIDTH = 0.75 # meters (IRC 5 Clause 101.41) +DEFAULT_GIRDER_SPACING = 2.5 # meters (preliminary design assumption) +DEFAULT_DECK_OVERHANG = 1.0 # meters (preliminary design assumption) +DEFAULT_CRASH_BARRIER_WIDTH = 0.5 # meters (typical) +DEFAULT_RAILING_WIDTH = 0.15 # meters (typical) + +def connectdb(table_name, popup=None): + """Mock database connection - returns sample data""" + if table_name == "Material": + return VALUES_MATERIAL + return [] + diff --git a/src/osdagbridge/desktop/__init__.py b/src/osdagbridge/desktop/__init__.py deleted file mode 100644 index 8523eba9..00000000 --- a/src/osdagbridge/desktop/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Desktop adapter package.""" diff --git a/src/osdagbridge/desktop/__main__.py b/src/osdagbridge/desktop/__main__.py new file mode 100644 index 00000000..067302af --- /dev/null +++ b/src/osdagbridge/desktop/__main__.py @@ -0,0 +1,36 @@ +import sys +from PySide6.QtWidgets import QApplication +from PySide6.QtCore import QFile, QTextStream + +# Import template_page +from osdagbridge.desktop.ui.template_page import CustomWindow +from osdagbridge.core.bridge_types.plate_girder.ui_fields import FrontendData + +def load_stylesheet(): + """Load the global QSS stylesheet from resources.""" + file = QFile(":/themes/lightstyle.qss") + if file.open(QFile.ReadOnly | QFile.Text): + stream = QTextStream(file) + stylesheet = stream.readAll() + file.close() + return stylesheet + return "" + +def main(): + # Create the Qt application instance + app = QApplication(sys.argv) + + # Load and apply the global stylesheet + stylesheet = load_stylesheet() + if stylesheet: + app.setStyleSheet(stylesheet) + + window = CustomWindow("Osdag Bridge", FrontendData) + window.showMaximized() + window.show() + + # Execute the event loop + sys.exit(app.exec()) + +if __name__ == "__main__": + main() diff --git a/src/osdagbridge/desktop/legacy_src/.gitignore b/src/osdagbridge/desktop/legacy_src/.gitignore deleted file mode 100644 index 7a60b85e..00000000 --- a/src/osdagbridge/desktop/legacy_src/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -__pycache__/ -*.pyc diff --git a/src/osdagbridge/desktop/legacy_src/README.md b/src/osdagbridge/desktop/legacy_src/README.md deleted file mode 100644 index 38f0be05..00000000 --- a/src/osdagbridge/desktop/legacy_src/README.md +++ /dev/null @@ -1,55 +0,0 @@ -# Osdag Bridge -## Overview -This repository contains the source code for Osdag bridge component of [Osdag](https://github.com/osdag-admin/Osdag). -## Technical Stack - -- **Framework**: PySide6 (Qt for Python) -- **Language**: Python 3.8+ -- **Architecture**: Model-View pattern with modular design - -## Project Structure - -``` -OsBridge/ -├── src/ -│ └── osbridge/ -│ ├── template_page.py # Main application window -│ ├── input_dock.py # Basic inputs panel -│ ├── additional_inputs.py # Additional inputs dialog -│ ├── backend.py # Backend logic and validation -│ ├── common.py # Shared constants and utilities -│ └── resources/ # Images and resources -├── README.md -└── requirements.txt -``` -## Installation - -### Prerequisites -- Python 3.8 or higher -- pip package manager - -### Setup - -1. Clone the repository: -```bash -git clone https://github.com/garvit000/osdag_bridge.git -cd osdag_bridge -``` - -2. Install dependencies: -```bash -pip install -r requirements.txt -``` - -### Dependencies -``` -PySide6>=6.5.0 -``` -## Usage - -### Running the Application - -Run as a module from src directory: -```bash -python -m osbridge.template_page -``` diff --git a/src/osdagbridge/desktop/legacy_src/src/osbridge/backend/__init__.py b/src/osdagbridge/desktop/legacy_src/src/osbridge/backend/__init__.py deleted file mode 100644 index 759b0ead..00000000 --- a/src/osdagbridge/desktop/legacy_src/src/osbridge/backend/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -# Backend package - diff --git a/src/osdagbridge/desktop/legacy_src/src/osbridge/backend/common.py b/src/osdagbridge/desktop/legacy_src/src/osbridge/backend/common.py deleted file mode 100644 index be929753..00000000 --- a/src/osdagbridge/desktop/legacy_src/src/osbridge/backend/common.py +++ /dev/null @@ -1,406 +0,0 @@ -# Constants for input types -TYPE_MODULE = "module" -TYPE_TITLE = "title" -TYPE_COMBOBOX = "combobox" -TYPE_COMBOBOX_CUSTOMIZED = "combobox_customized" -TYPE_TEXTBOX = "textbox" -TYPE_IMAGE = "image" - -# Keys for inputs -KEY_MODULE = "Module" -KEY_STRUCTURE_TYPE = "Structure Type" -KEY_PROJECT_LOCATION = "Project Location" -KEY_SPAN = "Span" -KEY_CARRIAGEWAY_WIDTH = "Carriageway Width" -KEY_FOOTPATH = "Footpath" -KEY_SKEW_ANGLE = "Skew Angle" -KEY_GIRDER = "Girder" -KEY_CROSS_BRACING = "Cross Bracing" -KEY_DECK = "Deck" -KEY_DECK_CONCRETE_GRADE_BASIC = "Deck Concrete Grade" - -# Display names -KEY_DISP_FINPLATE = "Highway Bridge Design" -DISP_TITLE_STRUCTURE = "Type of Structure" -KEY_DISP_STRUCTURE_TYPE = "Structure Type" -DISP_TITLE_PROJECT = "Project Location" -KEY_DISP_PROJECT_LOCATION = "City in India*" -DISP_TITLE_GEOMETRIC = "Geometric Details" -KEY_DISP_SPAN = "Span (m)* [20-45]" -KEY_DISP_CARRIAGEWAY_WIDTH = "Carriageway Width (m)* [≥4.25]" -KEY_DISP_FOOTPATH = "Footpath" -KEY_DISP_SKEW_ANGLE = "Skew Angle (degrees) [±15]" -DISP_TITLE_MATERIAL = "Material Inputs" -KEY_DISP_GIRDER = "Girder" -KEY_DISP_CROSS_BRACING = "Cross Bracing" -KEY_DISP_DECK = "Deck" -KEY_DISP_DECK_CONCRETE_GRADE = "Deck Concrete Grade [M25+]" - -# Sample values -# Type of Structure: Defines the application of the steel girder bridge -# Currently only covers highway bridge -VALUES_STRUCTURE_TYPE = ["Highway Bridge", "Other"] - -# Project Location: Cities in India for load calculations -# Organized by regions for easier navigation -VALUES_PROJECT_LOCATION = [ - "Delhi", "Mumbai", "Bangalore", "Kolkata", "Chennai", "Hyderabad", - "Ahmedabad", "Pune", "Surat", "Jaipur", "Lucknow", "Kanpur", - "Nagpur", "Indore", "Thane", "Bhopal", "Visakhapatnam", "Pimpri-Chinchwad", - "Patna", "Vadodara", "Ghaziabad", "Ludhiana", "Agra", "Nashik", - "Faridabad", "Meerut", "Rajkot", "Kalyan-Dombivali", "Vasai-Virar", "Varanasi", - "Srinagar", "Aurangabad", "Dhanbad", "Amritsar", "Navi Mumbai", "Allahabad", - "Ranchi", "Howrah", "Coimbatore", "Jabalpur", "Gwalior", "Vijayawada", - "Jodhpur", "Madurai", "Raipur", "Kota", "Chandigarh", "Guwahati", - "Custom" # Allow custom location entry -] - -# Footpath: Single sided or none or both -# Default: None -# Note: IRC 5 Clause 101.41 requires safety kerb when footpath is not present -VALUES_FOOTPATH = ["None", "Single Sided", "Both"] - -VALUES_MATERIAL = ["E250 (Fe 410W)A", "E300 (Fe 440)", "E350 (Fe 490)", "E410 (Fe 540)", "E450 (Fe 570)", "E550 (Fe 650)"] - -# Validation limits -# Span: Between 20 to 45 meters -SPAN_MIN = 20.0 -SPAN_MAX = 45.0 - -# Carriageway Width: Minimum 4.25 m as per IRC 5 Clause 104.3.1 -CARRIAGEWAY_WIDTH_MIN = 4.25 - -# Skew Angle: IRC 24 (2010) requires detailed analysis when skew angle exceeds ±15 degrees -# Default: 0 degrees -SKEW_ANGLE_MIN = -15.0 -SKEW_ANGLE_MAX = 15.0 -SKEW_ANGLE_DEFAULT = 0.0 - -# ===== Additional Inputs Constants ===== - -# Bridge Geometry Keys -KEY_GIRDER_SPACING = "Girder Spacing" -KEY_DECK_OVERHANG = "Deck Overhang Width" -KEY_NO_OF_GIRDERS = "No. of Girders" -KEY_DECK_THICKNESS = "Deck Thickness" -KEY_DECK_CONCRETE_GRADE = "Deck Concrete Grade" -KEY_DECK_REINF_MATERIAL = "Deck Reinforcement Material" -KEY_DECK_REINF_SIZE = "Deck Reinforcement Size" -KEY_DECK_REINF_SPACING_LONG = "Deck Reinforcement Spacing Longitudinal" -KEY_DECK_REINF_SPACING_TRANS = "Deck Reinforcement Spacing Transverse" -KEY_FOOTPATH_WIDTH = "Footpath Width" -KEY_FOOTPATH_THICKNESS = "Footpath Thickness" -KEY_RAILING_PRESENT = "Railing Present" -KEY_RAILING_WIDTH = "Railing Width" -KEY_RAILING_HEIGHT = "Railing Height" -KEY_SAFETY_KERB_PRESENT = "Safety Kerb Present" -KEY_SAFETY_KERB_WIDTH = "Safety Kerb Width" -KEY_SAFETY_KERB_THICKNESS = "Safety Kerb Thickness" -KEY_CRASH_BARRIER_PRESENT = "Crash Barrier Present" -KEY_CRASH_BARRIER_TYPE = "Crash Barrier Type" -KEY_CRASH_BARRIER_DENSITY = "Crash Barrier Material Density" -KEY_CRASH_BARRIER_WIDTH = "Crash Barrier Width" -KEY_CRASH_BARRIER_AREA = "Crash Barrier Area" - -# Section Properties Keys -KEY_GIRDER_TYPE = "Girder Type" -KEY_GIRDER_IS_SECTION = "Girder IS Section" -KEY_GIRDER_SYMMETRY = "Girder Symmetry" -KEY_GIRDER_TOP_FLANGE_WIDTH = "Girder Top Flange Width" -KEY_GIRDER_TOP_FLANGE_THICKNESS = "Girder Top Flange Thickness" -KEY_GIRDER_BOTTOM_FLANGE_WIDTH = "Girder Bottom Flange Width" -KEY_GIRDER_BOTTOM_FLANGE_THICKNESS = "Girder Bottom Flange Thickness" -KEY_GIRDER_DEPTH = "Girder Depth" -KEY_GIRDER_WEB_THICKNESS = "Girder Web Thickness" -KEY_GIRDER_TORSIONAL_RESTRAINT = "Torsional Restraint" -KEY_GIRDER_WARPING_RESTRAINT = "Warping Restraint" -KEY_GIRDER_WEB_TYPE = "Web Type" - -KEY_STIFFENER_DESIGN_METHOD = "Stiffener Design Method" -KEY_STIFFENER_PLATE_THICKNESS = "Stiffener Plate Thickness" -KEY_STIFFENER_SPACING = "Stiffener Spacing" -KEY_LONGITUDINAL_STIFFENER = "Longitudinal Stiffener" -KEY_LONGITUDINAL_STIFFENER_THICKNESS = "Longitudinal Stiffener Thickness" - -KEY_CROSS_BRACING_TYPE = "Cross Bracing Type" -KEY_CROSS_BRACING_SECTION = "Cross Bracing Section" -KEY_BRACKET_SECTION = "Bracket Section" -KEY_CROSS_BRACING_SPACING = "Cross Bracing Spacing" - -KEY_END_DIAPHRAGM_TYPE = "End Diaphragm Type" -KEY_END_DIAPHRAGM_SECTION = "End Diaphragm Section" -KEY_END_DIAPHRAGM_SPACING = "End Diaphragm Spacing" - -# Dead Load Keys -KEY_SELF_WEIGHT = "Self Weight" -KEY_SELF_WEIGHT_FACTOR = "Self Weight Factor" -KEY_WEARING_COAT_MATERIAL = "Wearing Coat Material" -KEY_WEARING_COAT_DENSITY = "Wearing Coat Density" -KEY_WEARING_COAT_THICKNESS = "Wearing Coat Thickness" -KEY_RAILING_LOAD_COUNT = "No. of Railings" -KEY_RAILING_LOAD = "Railing Load" -KEY_RAILING_LOAD_LOCATION = "Railing Load Location" -KEY_CRASH_BARRIER_LOAD_COUNT = "No. of Crash Barriers" -KEY_CRASH_BARRIER_LOAD = "Crash Barrier Load" -KEY_CRASH_BARRIER_LOAD_LOCATION = "Crash Barrier Load Location" - -# Live Load Keys -KEY_IRC_CLASS_A = "IRC Class A" -KEY_IRC_CLASS_70R = "IRC Class 70R" -KEY_IRC_CLASS_AA = "IRC Class AA" -KEY_IRC_CLASS_SV = "IRC Class SV" -KEY_CUSTOM_VEHICLE = "Custom Vehicle" -KEY_CUSTOM_AXLE_TYPE = "Custom Axle Type" -KEY_CUSTOM_NO_AXLES = "Custom Number of Axles" -KEY_CUSTOM_AXLE_LOAD = "Custom Axle Load" -KEY_CUSTOM_AXLE_SPACING = "Custom Axle Spacing" -KEY_CUSTOM_VEHICLE_SPACING = "Custom Vehicle Spacing" -KEY_CUSTOM_ECCENTRICITY = "Custom Eccentricity" -KEY_FOOTPATH_PRESSURE = "Footpath Pressure" -KEY_FOOTPATH_PRESSURE_VALUE = "Footpath Pressure Value" - -# Support Condition Keys -KEY_LEFT_SUPPORT = "Left Support" -KEY_RIGHT_SUPPORT = "Right Support" -KEY_BEARING_LENGTH = "Bearing Length" - -# Value Lists for Additional Inputs -VALUES_YES_NO = ["No", "Yes"] -VALUES_DECK_CONCRETE_GRADE = ["M25", "M30", "M35", "M40", "M45", "M50", "M55", "M60"] -VALUES_REINF_MATERIAL = ["Fe 415", "Fe 500", "Fe 550"] -VALUES_REINF_SIZE = ["8", "10", "12", "16", "20", "25", "32"] -VALUES_CRASH_BARRIER_TYPE = ["Rigid", "Semi-Rigid", "Flexible", "Other"] -VALUES_GIRDER_TYPE = ["IS Standard Rolled Beam", "Plate Girder"] -VALUES_GIRDER_SYMMETRY = ["Symmetrical", "Unsymmetrical"] -VALUES_OPTIMIZATION_MODE = ["Optimized", "Customized", "All"] -VALUES_TORSIONAL_RESTRAINT = ["Fully Restrained", "Partially Restrained - Support Connect", "Partially Restrained - Bearing Support"] -VALUES_WARPING_RESTRAINT = ["Both Flange Restraint", "No Restraint"] -VALUES_WEB_TYPE = ["Thin Web", "Thick Web"] -VALUES_STIFFENER_DESIGN = ["Simple Post", "Tension Field"] -VALUES_CROSS_BRACING_TYPE = ["K-bracing", "K-bracing with top bracket", "X-bracing", "X-bracing with bottom bracket", "X-bracing with top and bottom brackets"] -VALUES_END_DIAPHRAGM_TYPE = ["Same as cross-bracing", "Rolled Beam Section", "Plate Girder Section"] -VALUES_WEARING_COAT_MATERIAL = ["Concrete", "Bituminous", "Other"] -VALUES_CUSTOM_AXLE_TYPE = ["Single", "Bogie"] -VALUES_FOOTPATH_PRESSURE_MODE = ["Automatic", "User-defined"] -VALUES_SUPPORT_TYPE = ["Fixed", "Pinned"] - -# Default values -DEFAULT_SELF_WEIGHT_FACTOR = 1.0 -DEFAULT_CONCRETE_DENSITY = 25.0 # kN/m³ -DEFAULT_STEEL_DENSITY = 78.5 # kN/m³ -DEFAULT_BEARING_LENGTH = 0.0 # mm - -# Bridge Geometry Validation Constants (IRC 5) -# Constants for input types -TYPE_MODULE = "module" -TYPE_TITLE = "title" -TYPE_COMBOBOX = "combobox" -TYPE_COMBOBOX_CUSTOMIZED = "combobox_customized" -TYPE_TEXTBOX = "textbox" -TYPE_IMAGE = "image" - -# Keys for inputs -KEY_MODULE = "Module" -KEY_STRUCTURE_TYPE = "Structure Type" -KEY_PROJECT_LOCATION = "Project Location" -KEY_SPAN = "Span" -KEY_CARRIAGEWAY_WIDTH = "Carriageway Width" -KEY_FOOTPATH = "Footpath" -KEY_SKEW_ANGLE = "Skew Angle" -KEY_GIRDER = "Girder" -KEY_CROSS_BRACING = "Cross Bracing" -KEY_DECK = "Deck" -KEY_DECK_CONCRETE_GRADE_BASIC = "Deck Concrete Grade" - -# Display names -KEY_DISP_FINPLATE = "Highway Bridge Design" -DISP_TITLE_STRUCTURE = "Type of Structure" -KEY_DISP_STRUCTURE_TYPE = "Structure Type" -DISP_TITLE_PROJECT = "Project Location" -KEY_DISP_PROJECT_LOCATION = "City in India*" -DISP_TITLE_GEOMETRIC = "Geometric Details" -KEY_DISP_SPAN = "Span (m)* [20-45]" -KEY_DISP_CARRIAGEWAY_WIDTH = "Carriageway Width (m)* [≥4.25]" -KEY_DISP_FOOTPATH = "Footpath" -KEY_DISP_SKEW_ANGLE = "Skew Angle (degrees) [±15]" -DISP_TITLE_MATERIAL = "Material Inputs" -KEY_DISP_GIRDER = "Girder" -KEY_DISP_CROSS_BRACING = "Cross Bracing" -KEY_DISP_DECK = "Deck" -KEY_DISP_DECK_CONCRETE_GRADE = "Deck Concrete Grade [M25+]" - -# Sample values -# Type of Structure: Defines the application of the steel girder bridge -# Currently only covers highway bridge -VALUES_STRUCTURE_TYPE = ["Highway Bridge", "Other"] - -# Project Location: Cities in India for load calculations -# Organized by regions for easier navigation -VALUES_PROJECT_LOCATION = [ - "Delhi", "Mumbai", "Bangalore", "Kolkata", "Chennai", "Hyderabad", - "Ahmedabad", "Pune", "Surat", "Jaipur", "Lucknow", "Kanpur", - "Nagpur", "Indore", "Thane", "Bhopal", "Visakhapatnam", "Pimpri-Chinchwad", - "Patna", "Vadodara", "Ghaziabad", "Ludhiana", "Agra", "Nashik", - "Faridabad", "Meerut", "Rajkot", "Kalyan-Dombivali", "Vasai-Virar", "Varanasi", - "Srinagar", "Aurangabad", "Dhanbad", "Amritsar", "Navi Mumbai", "Allahabad", - "Ranchi", "Howrah", "Coimbatore", "Jabalpur", "Gwalior", "Vijayawada", - "Jodhpur", "Madurai", "Raipur", "Kota", "Chandigarh", "Guwahati", - "Custom" # Allow custom location entry -] - -# Footpath: Single sided or none or both -# Default: None -# Note: IRC 5 Clause 101.41 requires safety kerb when footpath is not present -VALUES_FOOTPATH = ["None", "Single Sided", "Both"] - -VALUES_MATERIAL = ["E250 (Fe 410W)A", "E300 (Fe 440)", "E350 (Fe 490)", "E410 (Fe 540)", "E450 (Fe 570)", "E550 (Fe 650)"] - -# Validation limits -# Span: Between 20 to 45 meters -SPAN_MIN = 20.0 -SPAN_MAX = 45.0 - -# Carriageway Width: Minimum 4.25 m as per IRC 5 Clause 104.3.1 -CARRIAGEWAY_WIDTH_MIN = 4.25 - -# Skew Angle: IRC 24 (2010) requires detailed analysis when skew angle exceeds ±15 degrees -# Default: 0 degrees -SKEW_ANGLE_MIN = -15.0 -SKEW_ANGLE_MAX = 15.0 -SKEW_ANGLE_DEFAULT = 0.0 - -# ===== Additional Inputs Constants ===== - -# Bridge Geometry Keys -KEY_GIRDER_SPACING = "Girder Spacing" -KEY_DECK_OVERHANG = "Deck Overhang Width" -KEY_NO_OF_GIRDERS = "No. of Girders" -KEY_DECK_THICKNESS = "Deck Thickness" -KEY_DECK_CONCRETE_GRADE = "Deck Concrete Grade" -KEY_DECK_REINF_MATERIAL = "Deck Reinforcement Material" -KEY_DECK_REINF_SIZE = "Deck Reinforcement Size" -KEY_DECK_REINF_SPACING_LONG = "Deck Reinforcement Spacing Longitudinal" -KEY_DECK_REINF_SPACING_TRANS = "Deck Reinforcement Spacing Transverse" -KEY_FOOTPATH_WIDTH = "Footpath Width" -KEY_FOOTPATH_THICKNESS = "Footpath Thickness" -KEY_RAILING_PRESENT = "Railing Present" -KEY_RAILING_WIDTH = "Railing Width" -KEY_RAILING_HEIGHT = "Railing Height" -KEY_SAFETY_KERB_PRESENT = "Safety Kerb Present" -KEY_SAFETY_KERB_WIDTH = "Safety Kerb Width" -KEY_SAFETY_KERB_THICKNESS = "Safety Kerb Thickness" -KEY_CRASH_BARRIER_PRESENT = "Crash Barrier Present" -KEY_CRASH_BARRIER_TYPE = "Crash Barrier Type" -KEY_CRASH_BARRIER_DENSITY = "Crash Barrier Material Density" -KEY_CRASH_BARRIER_WIDTH = "Crash Barrier Width" -KEY_CRASH_BARRIER_AREA = "Crash Barrier Area" - -# Section Properties Keys -KEY_GIRDER_TYPE = "Girder Type" -KEY_GIRDER_IS_SECTION = "Girder IS Section" -KEY_GIRDER_SYMMETRY = "Girder Symmetry" -KEY_GIRDER_TOP_FLANGE_WIDTH = "Girder Top Flange Width" -KEY_GIRDER_TOP_FLANGE_THICKNESS = "Girder Top Flange Thickness" -KEY_GIRDER_BOTTOM_FLANGE_WIDTH = "Girder Bottom Flange Width" -KEY_GIRDER_BOTTOM_FLANGE_THICKNESS = "Girder Bottom Flange Thickness" -KEY_GIRDER_DEPTH = "Girder Depth" -KEY_GIRDER_WEB_THICKNESS = "Girder Web Thickness" -KEY_GIRDER_TORSIONAL_RESTRAINT = "Torsional Restraint" -KEY_GIRDER_WARPING_RESTRAINT = "Warping Restraint" -KEY_GIRDER_WEB_TYPE = "Web Type" - -KEY_STIFFENER_DESIGN_METHOD = "Stiffener Design Method" -KEY_STIFFENER_PLATE_THICKNESS = "Stiffener Plate Thickness" -KEY_STIFFENER_SPACING = "Stiffener Spacing" -KEY_LONGITUDINAL_STIFFENER = "Longitudinal Stiffener" -KEY_LONGITUDINAL_STIFFENER_THICKNESS = "Longitudinal Stiffener Thickness" - -KEY_CROSS_BRACING_TYPE = "Cross Bracing Type" -KEY_CROSS_BRACING_SECTION = "Cross Bracing Section" -KEY_BRACKET_SECTION = "Bracket Section" -KEY_CROSS_BRACING_SPACING = "Cross Bracing Spacing" - -KEY_END_DIAPHRAGM_TYPE = "End Diaphragm Type" -KEY_END_DIAPHRAGM_SECTION = "End Diaphragm Section" -KEY_END_DIAPHRAGM_SPACING = "End Diaphragm Spacing" - -# Dead Load Keys -KEY_SELF_WEIGHT = "Self Weight" -KEY_SELF_WEIGHT_FACTOR = "Self Weight Factor" -KEY_WEARING_COAT_MATERIAL = "Wearing Coat Material" -KEY_WEARING_COAT_DENSITY = "Wearing Coat Density" -KEY_WEARING_COAT_THICKNESS = "Wearing Coat Thickness" -KEY_RAILING_LOAD_COUNT = "No. of Railings" -KEY_RAILING_LOAD = "Railing Load" -KEY_RAILING_LOAD_LOCATION = "Railing Load Location" -KEY_CRASH_BARRIER_LOAD_COUNT = "No. of Crash Barriers" -KEY_CRASH_BARRIER_LOAD = "Crash Barrier Load" -KEY_CRASH_BARRIER_LOAD_LOCATION = "Crash Barrier Load Location" - -# Live Load Keys -KEY_IRC_CLASS_A = "IRC Class A" -KEY_IRC_CLASS_70R = "IRC Class 70R" -KEY_IRC_CLASS_AA = "IRC Class AA" -KEY_IRC_CLASS_SV = "IRC Class SV" -KEY_CUSTOM_VEHICLE = "Custom Vehicle" -KEY_CUSTOM_AXLE_TYPE = "Custom Axle Type" -KEY_CUSTOM_NO_AXLES = "Custom Number of Axles" -KEY_CUSTOM_AXLE_LOAD = "Custom Axle Load" -KEY_CUSTOM_AXLE_SPACING = "Custom Axle Spacing" -KEY_CUSTOM_VEHICLE_SPACING = "Custom Vehicle Spacing" -KEY_CUSTOM_ECCENTRICITY = "Custom Eccentricity" -KEY_FOOTPATH_PRESSURE = "Footpath Pressure" -KEY_FOOTPATH_PRESSURE_VALUE = "Footpath Pressure Value" - -# Support Condition Keys -KEY_LEFT_SUPPORT = "Left Support" -KEY_RIGHT_SUPPORT = "Right Support" -KEY_BEARING_LENGTH = "Bearing Length" - -# Value Lists for Additional Inputs -VALUES_YES_NO = ["No", "Yes"] -VALUES_DECK_CONCRETE_GRADE = ["M25", "M30", "M35", "M40", "M45", "M50", "M55", "M60"] -VALUES_REINF_MATERIAL = ["Fe 415", "Fe 500", "Fe 550"] -VALUES_REINF_SIZE = ["8", "10", "12", "16", "20", "25", "32"] -VALUES_CRASH_BARRIER_TYPE = ["Rigid", "Semi-Rigid", "Flexible", "Other"] -VALUES_GIRDER_TYPE = ["IS Standard Rolled Beam", "Plate Girder"] -VALUES_GIRDER_SYMMETRY = ["Symmetrical", "Unsymmetrical"] -VALUES_OPTIMIZATION_MODE = ["Optimized", "Customized", "All"] -VALUES_TORSIONAL_RESTRAINT = ["Fully Restrained", "Partially Restrained - Support Connect", "Partially Restrained - Bearing Support"] -VALUES_WARPING_RESTRAINT = ["Both Flange Restraint", "No Restraint"] -VALUES_WEB_TYPE = ["Thin Web", "Thick Web"] -VALUES_STIFFENER_DESIGN = ["Simple Post", "Tension Field"] -VALUES_CROSS_BRACING_TYPE = ["K-bracing", "K-bracing with top bracket", "X-bracing", "X-bracing with bottom bracket", "X-bracing with top and bottom brackets"] -VALUES_END_DIAPHRAGM_TYPE = ["Same as cross-bracing", "Rolled Beam Section", "Plate Girder Section"] -VALUES_WEARING_COAT_MATERIAL = ["Concrete", "Bituminous", "Other"] -VALUES_CUSTOM_AXLE_TYPE = ["Single", "Bogie"] -VALUES_FOOTPATH_PRESSURE_MODE = ["Automatic", "User-defined"] -VALUES_SUPPORT_TYPE = ["Fixed", "Pinned"] - -# Default values -DEFAULT_SELF_WEIGHT_FACTOR = 1.0 -DEFAULT_CONCRETE_DENSITY = 25.0 # kN/m³ -DEFAULT_STEEL_DENSITY = 78.5 # kN/m³ -DEFAULT_BEARING_LENGTH = 0.0 # mm - -# Bridge Geometry Validation Constants (IRC 5) -MIN_FOOTPATH_WIDTH = 1.5 # meters (IRC 5 Clause 104.3.6) -MIN_RAILING_HEIGHT = 1.0 # meters (IRC 5 Clauses 109.7.2.3 & 109.7.2.4) -MIN_SAFETY_KERB_WIDTH = 0.75 # meters (IRC 5 Clause 101.41) -DEFAULT_GIRDER_SPACING = 2.5 # meters (preliminary design assumption) -DEFAULT_DECK_OVERHANG = 1.0 # meters (preliminary design assumption) -DEFAULT_CRASH_BARRIER_WIDTH = 0.5 # meters (typical) -DEFAULT_RAILING_WIDTH = 0.15 # meters (typical) - -# New Constants for Additional Inputs UI -VALUES_LOAD_CASE = ["Dead Load (DL)", "Super-imposed Dead Load (SIDL)", "Dead Load of Wearing Course (DW)"] -VALUES_DECKING_PLATE = ["None", "Type A", "Type B"] -VALUES_NO_OF_LANES = ["1", "2", "3", "4", "5", "6"] - - -def connectdb(table_name, popup=None): - """Mock database connection - returns sample data""" - if table_name == "Material": - return VALUES_MATERIAL - return [] - diff --git a/src/osdagbridge/desktop/legacy_src/src/osbridge/resources/resources_rc.py b/src/osdagbridge/desktop/legacy_src/src/osbridge/resources/resources_rc.py deleted file mode 100644 index 3199f23c..00000000 --- a/src/osdagbridge/desktop/legacy_src/src/osbridge/resources/resources_rc.py +++ /dev/null @@ -1,941 +0,0 @@ -# Resource object code (Python 3) -# Created by: object code -# Created by: The Resource Compiler for Qt version 6.9.2 -# WARNING! All changes made in this file will be lost! - -from PySide6 import QtCore - -qt_resource_data = b"\ -\x00\x00\x16H\ -\x00\ -\x00\x98\x93x\xda\xe5=ko\xdb\xb8\xb2\xdf\xf3+\x88\ -\xe6\xc3\xb6\x8b$\x95\xdf\x8e\x8b\xbd\x80\x938\xd9\xe0\xa4\ -I\x9b\xb8g\xb1\xb7\xb80d\x8b\xb6\xb5U,\x1dI\ -N\x9aS\xf4\xbf\x1f\x92\x22%R\xe2Kr\xdam\xcf\ -\x8d\xd04\x96)\xcep8\x9c\x17g\xa8\xd7\xbf\x82\xdf\ -j\xfd\xec\x01\x00n\xee\xce\xc6\x17\xe0\xe2\xc3%\xb8\x9b\ -\xfey5\xb9\xfb}2\x99\x82Cps{1\xbe\xbe\ -\xfc\xdf\xc9\x198\xf9\x13\xdc\xbd\x9b\x9c^\x9e_\x9e^\ -N\xff\xc4\x8f\xd4\x03\x02~}\xbd\xb7\xf7\xba\x09j\xad\ -#pqus2\xbe\xa2\xa8\x81\x97W\xd0MRp\ -\x17\xc1\x85\xbf\xf4\x17\xaf\x9a!\xf3+\xf8\x82\x1f\x04\xcb\ -p\x93\x1e.\xdd{?x\x1a\x81\x17\x1f\xe6\xdbM\xba\ -\x05w\xee&y\xf1f\xef\xeb\xde\xfb\xb7\xae\xbf\xf9\xc3\ -\xdfx\xe1#m?w\x17\x9fVq\xb8\xddx\x87\x8b\ -0\x08\xe3\x11\xd8_v\xf1\xf5&\xfb:\x8c=\x88n\ -\xb6\xa2\xcf \x09\x03\xdf\x03\xfb\xc7\x8e\xbblu\xb2\xaf\ -\xef\xddx\xe5oF\xc0\x89>g7\x22\xd7\xf3\xfc\xcd\ -\x8a\xde\xf9\xba\xb7\xf7~\x1a\x86\xc1\xd4\x8f\xd4\xe0\xce\xc9\ -O\xf68\xbb\xe7\x90\x1f\x0d\x0a\xe3s\x86B\x0e\xb1\x8d\ -\xbeo3<\x08\x19\x12\xff\xdf\x10=\x97\xdf\xccz:\ -\x8c]\xcf\xdf&\x1c\xd2\xff\x8a\xe20\x82q\xfat\xe8\ -\x06\xfejs\x0f7\xe9\x08\x8c\xf1\x9f\xff\ -\xd9\xa6i\xb8Q.\xa7\xc7\xb5\x9fBA<\xcc\x03\xd4\ -\x88[\xe7\x8f\x10s\x1a\xba\x1f\x06\xde\x1b #HO\ -\x14\x01\xbc0\xe1:\xcbE\x09j\x0eZ]\xf6L\x0a\ -?\xa7\x99d\x18\x81E.\x0fx\xdcGk<\x91j\ -\x81\xc0\xcb+\x838\x13\x07]\x02\x13\xc50I\xa0G\ -\x01U\xa9\xa1%\x9er\xe8M\xc5T\xf7(\xd7\xa1e\ -\xc6\xb8A\xe4\x88}\x0f\x82\x0b\x88\x96\xa0\xdb\x94=0\ -^T[\x9d\xa2\x89Fk\x04d\x94HJ\xcc\xb3\xff\ -HZ\x91U\x8fZ\xcd\xe6\xe4\xee\x81\xd0d\x11\x84\x09\ -\xa4\xdf\xd8)?\x15\x0d\xab\x1a\xa4\xba\x22\xab\x8a\xd0\x84\ -\xad\x89\x89\xbcco\xe0\x0d,\xfb\x129E\xd2\xdbb\ -\x89\xafJo<\x91L\x08M\x86\xadV\xbb\xa3\xef\xc2\ -\x88\xc7yk\xe0\x0c\xc6\x8c\x09\xa7k\x88\x84\xf34\x5c\ -\xad\x90~\xa5r\xa14\xd3)nB[<\x87\xacl\ -\xcb&\x88\x03b\xa0B\xbc\x9a\xbb/\xdb\xbd\xde\x01(\ -~\xb5\x1c\xe7\x15\x1b\xd1\xb5\xfb0wc\xc5X6\xe4\ -K3O\x92\x1f\xb5\x85\x94\xf3\x1a\x12Y\xe4\xdf0\xfb\ -'\x11\x91\x9b0\xbew\x83\xba\xccL[\x22\x9d-\xc8\ --\x1e/\xdad\x1e\xa2\xa1\xdc+[\xf1\xa24\x80\xcb\ -\xb4Bx\x81\x22\x06\xd2W\xe6Z-n+\xa8K\x9a\ -HPg\xadtH~LR7\x85\xbf\xbd\xc0\xe6\xc5\ -\x03|\xf1\x7fvZ@6\xb5\x0d\x84\x8bt\xc2\x9fe\ -\xc8\xca\xd9B<\xfd\x16\x19X\x0a\x8e&\xb6\x97\xc0\xcf\ -\xbc%\x9e\xabTd\xc1\xa5k|#\x1fdCe\xd6\ -.0\x9f\xf4\xf0\xf5F\xab\xfes\xc2\x0d3\xc2\x89F\ -R\x8by\x10\xa5\xf9\xe6\x06%\xb2d\x15\x09\x05\xc7\xf0\ -=\x18E\xa2\xccZh\xd7\xb7\x16x\xa0\x1f\x13\x18\xc0\ -E\x0a\xbd\xdf^\xa4\xf1\xb6\xe0R\x09a\x1b\xe3\x93\xf1\ -\xc6\x19r\x05\xc0\xe96A|\xa5`\x11\xec,\xcc\x16\ -\xa4\x85Q\xf2\xc9@;\x0aw\xae:\xc9-\xc7\xde\x84\ -\x93\xa0e\x9e\xa9\x81w\ -!\xbf\x1a}\xb01\x90\xb4\x22D\x06\xe6\xeeaE\x9d\ -m\x0a\xca_H\x96p\xd5\xa1nD\xf3\xfe\x11\x98\x8e\ -O\xc0\xc9\xf8\x96R\xbb\xa1\x9f9u\xe7'n\xbc\x7f\ -\xef\xfa\x9bY\xea\xce\x93\xd1\x08\xfd\x96 \xbd\x7f\xde\xc5\ -\x97\xc0\x8eX\xf5\x09\x0b\x88Y\xe2\xfcb\xcc\x96\x85\xbe\ -QY#K`I42\xdf*_\xae}\xec\xa1b\ -%\xc6\xfeP[\x87\xfcbn\x89*\x8f\x0e\xad\x88\xd5\ -\xc9\xa84b\xbaCF.\x93ij\xf0v\xed\xacF\ -\xdd\xa8\xd5hW5\xb5\x8dIT\x99lI\x9b\xead\ -\xf3j\xbf\x8a\x0d\xf1\x88\x0e\x05]\xe7\xdf\xbb+4!\ -\xdb8x9z\xfd\x80\xc8\x1b\xc6\xc9k\xe6\xca\x11\x07\ -*\xc0@\x8e\x92\x87\xd5\xab\x0cv\xb2\x9dS\x1f\xef0\ -D\x08`{\x85\xd2\xa5\xf2}\x14&>\x8eu1]\ -\x07\x08\xc26\xd8\x09D3\xe3HZS\x1c\x1b\x06\x97\ -\x06G\xe0\xfcv\xfcv\xb2\xdb\xf2\xa6\x02\xe9\x84\xf0\xd1\ -\x95\xbf\x81:\x93\x22\x0b_cbd\x81Aj\xce\xaf\ -\xa1\xebi\x15\x15\xe5\xc8\xe2A\x1a\xbd\x93G\x14\xed\x8c\ -V\xd8\x86\xc3\xa5#\xd7\xeb=\xc6\xdf\x19\xb4\xbb,\x08\ -)rt\xd5\x08\x90\xba\xa8\xa2\x87\xfa\xcahCW\xa0\ -2/\xc7\x83K\x17i\xdd\xc2\xcd\xf9{\xd0`\x22I\ -\xe3nap\xadn\x17y\xe3\x03\xec\x92\x1f\xbf\xb22\ -\xa8\xa5\xe3Q\xa2A\xf8\xff{\xe3 \x9a?\x88\xb9\xee\ -%\x82Y\x1b\xf7S\x07\xb4ZU#\x1a\x1b\x86f\x14\ -\xa4~\x11/ \xa9)WQ g\xbdI\xf7\xf8\xa4\ -\xb9\x810<\x02W\xe3\x93\xc9\xd5n\xf2#3\x9d\xd0\ -\x08\x12\xbfd;\xd1\x99\x5c\xc5\xeeSF\x81\xac\xe9}\ -\xe8m\x038K \xd9T\x90=\xa1\xd4\xc1}\xed\x12\ -\xa9\x06\x93z\xce+\xc9N\x97\xf3\xac\xde~10\xb6\ -\xd5\x91\xfai\x1e`\xab\x06\xd9\xcdf%?\xe2N\xc9\ -\xea \xb8\xe6\xd2-\x83\x8bz\x81\xf1\xcc\x83\x0f\xf5i\ -YE>\x8c\xa0\xca\x026\xed*\x00\x9bm\x052\x00\ -n\xf5\x0e\xe6n\xaf\xdd\x13\xdbd\xd4&\xf6\x84\xb8\x94\ -e\xad\x88\x82\x96HQ1\x8a\x5c\xd9q\xd5\x8d\x5c)\ -\xb1\xff\xff\x91\xc2\xe0\x82f\xe3\x07\xca\x10\xe2\x7f\x113\ -X\xe8M\x095\xec\xf7\xdb~ Z\x14\x01\x99\x9b\xcc\ -L\x03\x848\x09'\xef\xa3,D\x93\xfc\xce[~\xb2\ - \x8a4\x9b@!\x15\x85\x10#'\xec\xed\x0d\xa5\xfd\ -\xe5`\xe9.\x17&\xd9\xcdL\xc7\xb2R\xfa\x19\x86S\ -\xd6V\x123\xd8j\xc4\x9b0\x8b\xb6%\xa5\xd1\x0eZ\ -C\xe7\xb8\xaf\xd1D\x85.\xed\x8ak'\x8a\x91\xdb\x13\ -?M\xe1\xe7\xb4\xd4g\xcbm;\xed\x85\xa6\xcfj\x80\ -\xcc\x98c@\xd7+D\x9e\x9b'\x87\xaa\x1a\x89\x84\xe0\ -\xca\xfe=\xb4\xf6%]\xbb\x8e\x0b\x17NM]\xde\x12\ -\xc9\x85c?W\x12\x85\xadb\xb7\xbe\x11m\x05/6\ -\xb5\x0f\x8f\x8f\xc0\xdd\xe9\xed\xcd\xd5\xd5\xce!$\xab\xec\ -\x0d\xda\x0a\xb9\xd8#\xf4e\xea/\xdc\xa0d\x14s\xbb\ -jB \x85[$twDj6\x0dK7\xd92\ -\xc9\x83!u\xd1\xa4\xd9L\x15ty\xe4\x16s\xaf\x07\ -\xe5^j\x91i\x82\x90Y\xd3)+\xf6Tw\xc5F\ -\xa1\xba9\xeem\x04\x05\xad\xfe\xc3\xc0\xdf\x14p\x0e@\ -\x83^\x92\xed\x5c\xec\xc5j\xaaY:PC\xb4#\x1c\ -\x94\xd9\x19m\xa1\x17\x09y\xa5H\xc6\x10\xc7\x93\x92\x9d\ -\xd2\xa0$\x1b4TY\x93N\xf1^\xb9\x9f\x223e\ -Y^s\x12\xe0;\xac6\x07_\xc2j\x1bJ\x0c\x09\ -\xfe\x1f\xc3\xf3\xf7,\xf9\xaf\x1evV\x8b\xcc\xc1\x97t\ -\x91u\xb5\x8b\xac@*\xdc\x80l\xbd\xec\x86\x9dv\xd1\ -\xa1\xab\x04\xf5q\x0d7\x80\xed\xbe\xec\x08Y\xb5\x89\x83\ -`\x0f\x1d|\xbd\x01\x08\xf0\xe4\x01AD\x0a\xf3\x13\xc2\ -r\x15\xc3\xa7\x0a\x0a\x19~\xb7\xf0\x1e\x0d\x04 n{\ -\x8dx\x1e\xe0\xa5\x0a\xe64\x7f\xe8\xa5\x1b\xc7\xe1c\xf2\ -\xaa.\xc2\x12\xc9\xf1\x8c\x22\x01\xaf\x83\xf4\x09u\xb0\x02\ -\xe9\x1a\x02\xbcJ\x01A\xe1%\xfe\x88\x96\xd6\xe2\x13\xfa\ -\x8c\x1f\x22\xdfg\xd4k4\x86\x92\x18i$\x1f\xf0T\ -\xbcu?\xc1\x12\xae\x9c\x048\x00\x01LS6\x9c\x02\ -\x08\xd7\x17H\xd6\xe1#\xfa\x1a}X\xad\x8b\xb9;\xcb\ -\xdcV:_ AT\x81\xe5M\xb0i\x18)r\x07\ -\xb3p\xad]Z\x05g`\xf2a\x06.\xf4P5c\ -\xeao5\xe3\xe5BV\x95\xd5\x80>\x92\x15X\xda\xac\ -\x17\x86gH)\x1c\x8eO\xda\x9dq%\xd7\xe4\x8d:\ -%Cn\xbd\xd6\x1a\xe0;\xba\x00\xed\x86H\x97\xabn\ -\x90\xfc(~\x94A\x9e\xc5a\xe4\x85\x8f\x1b\x80\xd3+\ -\xb81\x92\xa4\x183\x1f*\x83tR|\xabY\x84\x18\ -\xcch\xc4E}\x15\x0e\x1a\xa3G\xa7\xd3Q\xa9a.\ -\xed\xa5\xdc\xb7v\xcfO\x91\xc7\xa46\x0fq\xdfB\x0a\ -\xfa)K\xce\xa6\xdd\xab6GZ\xb0\x07\x8f\xdf\x94\x9a\ -\x14}\xe7d\xd2D\xbf\xcbZ\xb3;\xe4\x1f\xe2\xe9\xcb\ -Lh\x05\xaa\x1f\x97\xe1b+\xe1W1e\xe1tx\ -6\x1c\xb7I7x\x1bj\xe2\xf9\xac\xa3K\x9c>o\ -\xa5\x0d\xb4\xfe\x97|\x82\x15\xe0F\x04g\x0a4\xdc\xa6\ -D3U\xf2\xfa\xb8\x0cu\xea\x8f\xae\xc38]l\xd3\ -\x7f\xc0\xa7\x03\xb0\x1f\xa0\xff\xa4\x8c\xdd\xc3\x97\x92\xb7=\ -\xcf\xd3\xdb2l\x00\xfd~_\xb3QmX\xb1m\x96\ -\xc7\x95#\x1f\x05\xdbD\xe6\x91\x0e\x87\xd6\xd1\x8f\x1eK\ -n\xc9v;h\x98\xe8\x12\xad\x0b\x19\x19\x86\xcb\xe3\xa5\ -\xab$\x03\x1f\xd9`\x0b\x8e\x22Mv\xb1\xbb\xaa\x88\x0a\ -]\xf1U\x1c2\xeb\xec\x8bz+E\x9bIS\xedO\ -\x95\x1b\xd6\xa0o!\xacv\x8d\xe0\xd4\x88\xa0\x14\xdc\xb7\ -\x9dg\x81,\xdc\x01vp\x8a \x86l^U\x01\x92\ -R\xa0\x02\xd9D\x08m\x92\xab\x22\xec\x0c(\xc2^&\ -\x94\xfb\x924\xc1\xcc\xec\xb2N\xe6\x02\xb5\xb2\xb9\x80!\ -\x9d\x0b\xc8\xf3\xb9\x804\xa1\x0b\xc8\xd7\x16 \xb3\xa0\x1e\ -\x91]*\x17\xd0p\x89\x11\x82e\x12\x17\xe0\xd7g\xc6\ --\x97\xf2\x8d\xd0\xef\xb4>\x0b\x1c\xd4\xceS\x9d5T\ -\xac\x00\xd0p\x0d\xbd%=\x90\x8f\x07`\xd75\xc4\xb6\ -}\xc9n\xa7\xaa\x82J\x92\xa4\xa8T\xe7ej\x0eE\ -\xf43@8\x90\x8d}\x07Ye\xe3)2\xce\xe6\xb1\ -\xff\xe2\x00$H[\x1e&0\xf6%\x86\xdf\xd0\x10\xdd\ -\x96\xa6,\xe9T1om\x09\xc6\x02\xf1l\xcavM\ -\xcd\xb8\x08\xcf\x7fm\xca\x7fm-\x9c\x19\xbc\x8f\xd2\xa7\ -gd{\x01\xac\x8e\xed)>wQ\xe0&k\xe4\xd7\ -A\xb8\x99\xe1b\xc9\xd8\x0d\xb2\xaft9\xd2E\x01\xa9\ -\xb2N\xabX\xf9B\x1aRq?y\x9d\x10\xd8\xb3\xf9\ -\xea\xe8\xaf\x88eIq\xcf\xc50\x82.)y\xa0\x7f\ -VZ\x94\xf3\xa4x\x0e\xcc\xfa\x0eB\xc2\x81\x82\xc60\ -l\xdd\xb3\x7f\xceQ\x87\x15\x81\xfc\xb6\xdbO\xc6\xa9\xae\ -\x07\x1e|\xf8\xc8\x02\x18{;vZr\xcbP\xf73\ -\xdc\xfd\x8c\xc5G\xbe\xecPP&\xf3\xbaUY\xfe\x85\ -\x05\xae\x09\xfd\xeb0\xfd\x86\xb5wZ\xb0&M\xa5%\ -\x94\x0cl^\x8d\xb73\xc3\xe0tbI\x85\xee\xcc\xdf\ -x\xfe\xc2M\xc3XV\xd4[|k\x1a\x92\x8e\xa7\xa7\ -H \x05H\xcb\x1c\xbeC\x8b\xd6\x12\xc5\x94>3#\ -!$\xab\x14xc\xdd9\xeb;/\x8bK7\xb4l\ -XS\x8a&K\xb4e\xfda\xdf\x18'N\x0a\xc8f\ -\xa5\xc68fm\xd5\xa9\xb6v\xcb\x0e\x90\xdc\xf5'\xf5\ -\xaa\x8e\xd5\xd6\x9a,\x1af\x09U\x1b\x14`U\xfc\xb6\ -}i\x02\xbd\x93!\xbel\xa8.\x0f\xbaHO\x16\x90\ -\xacAV\xa6\xafsR\xeb\xf2\x81&>\xd3\xab\x95\xa4\ -]\x0b\x94zf\x8ab\xcd\xfe\xb9#\xd2\x22\x1fl\xa7\ -\x16\xbc\x04\x22v\xe2\xc4\x04\x0b\xac\xb4d\xec\xb7\x8f\x80\ -\xa2K\x92s^\xaaPg9\xd5m\xd9\x9e\xa7\xa3\xd8\ -\xf3\xac7'Y*\x09\x09\xfbS\xd4\xcb;?l(\ -%\xdb.%u\xa0\xb3$\x8du\xc7X\x1cw\xe7\xc3\ -V\xbf\xac6\xf8g\x8d\xd5N\xfd\xc5\xb0\xeb\x0cU\xb5\ -\xc7\x8d\xb26\x14E\x84F$MZ\xb5\x07\x07]g\ - \x84\xf6\xb0\x86\xa4\xa5\x22\x19;\xe6,\x22,\x01 \ -\xfe\xf0\xb2Z\xd2\xc1\xfb\xd35\x5c|:\x09?k\x12\ -\x02\x992(\x15\xf9\x1f\x92\xd8\xec([\xed\xc2\x17\xac\ -\xfa\xb0\xd4^VVj\x89\xdd\xc8\xf3\x13w\x1e\x94K\ -\xf3\xb3=\xab\xfe\x8f\x83\xa6\xa4&\xa2\x22\xf2\x9e\x15-\ -K\xbcFe\xe3\x83\x01i\x97\xd7f\xab\x1cQ\xb6\xed\ -x\xb4\xc0\xf7`)\x7f\xbe\xe1(\xff\x85#\xa9h>\ -1\xf1\xd0H_\xe6\x8eA\x82\xf4\x9a\xeb\x8d\x10\xc7\x1f\ -\x80\xcf\xad\x91s\xd4;\x00O\xadQ\x0b}j\xd3O\ -\xe8\xff\x83\xa2=\x16pN\xe6=\xb4\xda\xce\x01(~\ -\xe1\x0c\xf9R\xc3\x16m8\xc4m\xf2_\xb8!i\xf7\ -J\x95\x82\xf5\xf7\xa2\xdbv\xf0\xd7\xc5/\x0e\xdd&\xf3\ -\xb8\xdd\xfc\xf83\xd9\xc7T\xc9\x7f5\x22\x8d\xb1\x0a\x85\ -\xb7\xc1\x97\xc8Q\xc5\xdb\xac\x87Y-\x8b\x95\xf7G\xe9\ -\xce\x1e%&\xf2B\x19\xc0\xb05\xaa\xe4\xb5\xf7b\xb6\ -g\xaf\x9a\xbb^J\xf4,\xb7\x90\xe5\x8c*\xdaH:\ -\x12\xb5\x1d?\xe0FI\xd2\xd6\x85\xc8\xc2\xe1;\xe2F\ -\xa3\x1dZ\x16\xe9\xba\xfb^k\xd9\xea\xbbo\xacN\xbd\ -q\x9e\x0d3\xf3i\x07\xdf\x06/\xa5\xe3y\x15\xae\x92\ -C\x5c@k\xc7\xf5\x01jN\xdc^\x1a\x1a\xd5\xf0{\ -\x1b_\x16\x0e\x5c\xc7`\x93U\xec\xb7\xaf2lp\x9e\ -&\xde\xc6\xd3 4\xc4\x97\x95W#\x84L\x7f9\x0d\ -\xb7\xb1\x8f\x96\xf65|\xfc\xe5\x00\xdc\x87\x9b\x10)\xea\ -\x85\xce\xba4\xfb.\xd21\xa8\xb3\xb2\x047\xcf\xc1\x17\ -I\x22\xb9\xc2\xb4\xca2y\x96H\xec\xe3\x5c\x91$O\ -\x07\xcb\xb2^\xd0\xdc\xd9&ku\x14\xebRM\xf4z\ -\x89Zc\x07_Y\xf6\x0bD]\xdf\xab0_\xe7\xa9\ -b\xe5]\xe8\x8e\xf3\x8c8\xaaw\x1c\x06\x0e\xbe\x08\xa6\ -g\x5c\xb6T\x9e&\xc6P.\x105\x81n\x98\xfe\xc4\ -\xc6\x8d\x87\x8d\xb1\xf9\xdd\xf7`B@o\xa3\xd7$\x9b\ -\x22K\xc7\xb2E\xe1\xb9\xb2\x97\x0a`x?\x0f\x91\x0f\xf8\xa5\ -\x92\xf9S\x7f\x1b\xaa\xa7.SR\x1e3\xa9Ej4\ -\xf2b\xe4\xbd`\x15\xf6\xa5\xf91\x0d\xc8\x01bg4\ -T\x8e\xa2\xa0,\xa6\xc7\x01\x81\xcf\x22\x9b\x92\x93\x1b\xc8\ -\x87\x84h\xd9\x19iC\x8f\x96\x886l\xd3\x94y\xc8\ -\xbdJ\xb0\xa5'\x8f\xd2\xf6\x8c8\x81\xf7\xe3y\x82-\ -7\x92b\xf4O\x1f>\xee\xba\xa1XJ_\xab\x09=\ -\x8b\x93\xeftnf5m\xce\xa6\xa2^\x92\xb0\xd6\x10\ -y!\xef\xcb\xe0x[\x1d>g\xc3\xdfJd\x98k\ -\xfa\xa5>@\xc3\x00v\xc3G\x9e\x1d\xb7\x03V,\xac\ -\x0fdh\xb1\xa4\xcbr\xa0\x1bw0\xd0%\x9a\x13]\ -*e\x98~\x1d\x11U\xddC*\x1f\xf4(\xa5\xe6\x05\ -\xea6\xcaE\xaa]\x14\xa7Z\x8b\xc2\xef\xd0\x1c\x0d\xe1\ -\xbdf\x03_\x83\xc3hD\x8e\x1aPJOz<\xb5\ -Azf\xc7\x13\xe2&\xf4\x00\x1f\xc9\xce\xb0\x1c\xf5C\ -]\xed*w*\x96\xb0\x9f\x9e\xccp\xbbHv@5\ -kPG\xeb\xedU\x9fR\xf3V\xbb9ou\x9f\x9d\ -\xb7$x\xb3\x89\x05\xdf\x8f\xbb4XP\xf6\x02?8\ -\x7f\xc9\x86P9\x1b\x9bw\xda\xc0\xb1\xa6\xe2:\xcfu\ -\xae\x1f\xbc\x14J\xcd\x98a\xe0p\x01\xba\xcf\xf9\xdd\xb6\ -\xa33,\x8c\x07M\ -R1/%\x91b/Vr\x22\x8a)\x8b`\x90\x1f\ -,\x9eG9\x00E\x9f\x98{\x91\xbb1\x9fYRH\ -#\xc0\xc6\x97\x19f\xd6O\x9f\xf9n\x10\xae\xf6\xe9v\ -\xcb\xbd\x9b\xc2\x18\xdd\x99Ea\xb4\x8d\x9a\x1fC\xac\xd7\ -\xe9r`Y\xf4\xf5 G\x09G$q\xa8y\xe1\xe2\ -?\xd2\xa7\x99\x07\x11\xf7\x04\x89\xb2\xa1G>\x8aQ\xdc\ -j\x90R\x09]\x94\xb1\xcf3hM!\xa7\xe4\xfc\x81\ -\xa2\x80\xc1\x84\xe4G\xbc!u\xb3\x09\x9e\x94\x89\xfa|\ -&\xb6\xb3\xb4\x1a\xb9P\x22\xa2y\xdb\x87\xba'\x8b#\ -\xfc\x85\x94O\x19e\xc5\x13\xfeT\xa7Lt\xd4'\x22\ -\xcb\xf5\x82\x11cS\x9a\xc3\xc0=v[m\xcb\xbe\x8c\ -\xb9\xeb\xbd\xe5\xc0u\xaae\xd5T|\xadMq\xc0\xb5\ -2\x0ehQG\xf4\xd5\x00\x92w\xf3\xd7H7\xfe\x1b\ -\x8b\xed\xc0\x8c\x88\xf41S\x98OH\xb4)\xed\xfb\x88\ -5\xd7\xb5\x82dk}\x84\xab\xe1\xb0\xaa\x8f\xdb\xc4\xfa\ -\xd8\x19\x0e\xdfp\x14\xba`_\xcdNj\x87\xfb\xd6\xa6\ -\x80\x1bGlyD\xad\xe9l4\x81 &\x958\xb5\ -I\x9f\x87\xcdt0\xcb\x0dj\x8f\xaa.\x04]\xecM\ -\xb9S\x91\xe9\xcb\xc4\xb0Kj\xd4\xc6\xc5\xd0\xd4\x19\x87\ -\xdc\x82\x97l\x1b*\xf4x\xd1\xb1Z\xd7\x0b\xb5\x104\ -\xad\xb7\xd1\xeb\xa5\x0a4\xa6\xdb4\xc4b=\xc9>\x17\ -\xb0\xc7\xc9\xa7\xf7[\x98\x90W\x07qw\xe7hBo\ -\x12\xcf]\x95\xdb\x7f\x88p\x91M\xf9nv\xa8s\xf9\ -\xae\x07\x13\x7f\xb5\x99\xc50\x0a\xe34S(\x07%\xe3\ -\x0c\xd9\xd4\xde\x8cp\xa7\x8a\x1c\xbb&j|5\xcf\xb6\ -\xd5\x14\xebA\xd0)\xcb\xe80\xc5\x1e\x19>\x05\xe0\x0b\ -0\xbd\x86\xe5k\xcd\xb3]s\x80\xb4\x8c\x0aid\x9c\ -\x0b\x1a\xcc\x83\x96\xee}\x04\x86\xf2\xa0.\xf9\x91\xf7\xdb\ -\x96\xf4[\xa9\xe1\xeb\x91\x1f\xae\x03B\x82+\xc3I\x87\ -\x92,N\x8dz\xcf:\xbe\x0aW\xe1U\xcd\xb3\xbf\x05\ -S\xacB\x1f\xf6\x127\xeaQ\xbd\xf57>fK\x83\ -\xc9\xa7\x84%3\x9e\x1d\xd3\x1b\xda\xe4GHIS\xd9\ -5\xc8\x1a\xeb\x17[\xf82ub4\xf3\xdc>\xbe\xaa\ -\xdd\xb8\x9fo\x91 \x09\xe3\x9f\x84t%t\x9b\x12\xaf\ -\xdcMS\xf2\x9d\xe2\x13\xa7\x7f\x0a\xcaq\x98\x9a\x88\x06\ -\x07\xddEg!_\x8c\x9a^\xcdowr:\xc7\xed\ -\xb9\xe0\xec\x97UV\xaeFO\xb3\x00X\xf6I\xae\xc8\ -Lm\xcb\xbaJ\xde\xde\xe6hm5\xb681\xea\x04\ -\xe7b\x10\x93W\x8a$\xd7\xa4\xce\x9b4\xc4\xc2H\x07\ -_\xfap\x8e\x98V5\xc6v\xc3/\xda:\xd4a\xa4\ -*\x1fU\x0e\xb7\xf0+U\xa3\xb5\xf3\xbbU\xd5mV\ -\xef\xc6\xea\xc9\x12\xc1\x80&\xee\xa7\xae\xe0S\x9b<\xf2\ -\xb1\xaaM\xcf\x9f\xf7\x95\x816\xb3\x9d;u\x8697\ -F.\xc6\xc7\xfdV\xcb\x1a*\x95(\x16p\x8d\xb2\xa7\ -\x7f2\xec9\xe75&\xbd\xf9\xabaw\x9fJ\xb9\xbd\ -Ug2\x0d\x83+\xcf\xa7\x0d_\x7f\xcb\x92VK\xac\ -+\xfc`\x85\xf7\xb7z?\xa5\x16k\x16\xc9\xc4\xa9,\ -\xbav,\xb3V\x13Cn\xb8\xe36\xee\xe3\xeb\xfb\xec\ -\xb8\xe5\xfe\x10\x1d\x1cS\xbd.9\x87\x9e\x96\xb8\xbe\x9f\ -\xc6\x10J\xcb\xf0\xedv\xe1\xfa\xba\xd3|9\xfd\x89\x8f\ -n;\xf4\xf0\xc9*\x04\xfca\xbe\x09\x0fZ\xbc\xebf\ -\x89\xaa\xbcl\x92\x1e\xc4\xa1\xc6\xe7\x18_\xc6\xa3\x83\xa9\ -Zo\x88\x94E\x81\xa5w\x0c\x8f\x17\x03S\x96t=\ -\xa8&\xeb\x11.{\xb0\xdf\xac{E\xbdU\xb7\x92\x02\ -$\xeeV5\x80\xa0(\xd8\x11\xa6\x87\xf9\xc7\xca#E\ -vB\xa09x~\x8d\xd0\xa4\xaa\xbc\x80\xd2\x06\x9f\xec\ -(\xee,M\x85\x1ej\xa2\x99\xce\x16\xbe\x8c\x95\x064\ -/\xacgr_T\xd57}c\xf5M\xbf\xd1\x94\xf3\ -\xa3\xad\xbcv\xa9!\x0a\xa50f\xe1g\x1cf\x11[\ -c\x04\xb3\xea\x99da\x92@\xdcM\xd46\x9b\xb5\xf5\ -gEI\x0f\x03\xe7\x8eg\xcb\x8f\xe0+'\x81\x99\xa0\ -o\xc2\x14\x1a^c!\x11\x7f\xb6\xee\x8c\xee}\x89\xe5\ -\xb8\x95\xaa~E=\x00?1y}\xfa\x032<\x07\ -_\xf6^\x89-:\x82\x92\x11\x12-\x8b\x13j\xaa\x87\ -&4\x8b4TK\x83JG.\xda#lq\xf4\xd6\ -\xa0D.\xb2\xe2\xaao\xbd\xaa\x01\xd3\xac\xf4\xe0p\xd9\ -]\x0c\xf5\xe9t\x15\xf2JB\xcf5\x901\xbe&{\ -\x08\xbb\xf3\xa1\x9a\x12\xc5\x96n\x0d\xe0\x8a\xf2\xeec\xf2\ -\xa36M\xe8\x09\x89:Pv5\x12\x92\xf3\xaa[\xd5\ -7_\xa8W\x8b\x1d\x06uO\xa9\xb6\xaai\xd8\x01\xbe\ -zo\xd2\x86\x83\xb8,\xa0H\x12`\xb0|`\xd6\xae\ -\xffH\xa7\xfe#\xdd\xfa\x8f\xf4\x9a\x86},\xd4X\xd7\ -T\xda\xaa9\xd4\xdd8\xe1\xd2\xe1\x94\xfdd\xeb\xe9i\ -\xfa`\xa7\xe9\x83\xdd\xa6\x0f\xf6\xec\xb3M\xea\x12\xaf\xe2\ -\xae\xdb\x93\xaf\xf1\xa3\x9d\xe6\x8fv\x9b?\xda\xab\x93f\ -S\x97\x8cL\xcc7\xa1c\xf3g;;<\xdb\xdd\xe1\ -\xd9\x9eE\x16&o\x86U\xb30\x95Y\x06c$\x0b\ -\xb0\x9b\xe0\x06\x87\x97\x1aC]g\xb9\x17]d=<\ -\xc3>7\xf5g\xa6x\xcc\xccgA\x1f\xd8\x9f\x7f\xe8\ -\x13\x09\xaa\xae\xa8\xa2\xa3\xffa]\xc9\xfej\xba/\xc1\ -\xca\xba\xbf\xeeU(\x03\xe8K]k\xbc\xd3X\xf9\xa6\ -\xe2&/\x18\x96\xbdKX\x8f\xe4s\xbfR\xb8\xfa\xf5\ -n\xaf\x146`ox\xd7e\x11\xf5y\xfd+(z\ -\x02\xd9\x09\xe0\xdc\xfb\xe0\x15\x9c\xfe_\xb1\xc5\xa1\x19\xd3\ -w\x08ok\xc1\x7f\xeb(u\x15\xf8\xcf\x10XVc\ -\xaf.\xc0\xfc.\xc8[\xd0\xf7\xbbWcZ\xa1\xf2\xf7\ -\x14e\x9aQ\xfb\x0e\xb5\x99M\x90\xf8\xd1J4\x9b\x8f\ -\xe1\xdbUj\xee\x80\xd3\xb7,\xd8|\x06\xb4\xbeY\xdd\ -&y1\x0d9\x92\xf6\xc2\xc7\xad\xc0\x099=\x1a\xd0\ -\xadmN\x0fg_\xdc\xd1\xc3\xc7\xe8\xf7\xcfx\x0eV\ -\xf9\xccU\x1dX\x8b\xca\x14\x03\x02\xa7\xe4\xc7\xb2.\xa9\ -\xc7^\x7fb\xccdd\xf1l[\xfc\x8d/\x06\xa7!\ -4\xeb\xfe\x8c\xf5*\xda\xc5u\xdeC4\xb3\x01\xa7+\ -'\xfa\x0f\xdfM\xba\xd4\ -\x00\x00\x02\xfe\ -<\ -svg xmlns=\x22http:\ -//www.w3.org/200\ -0/svg\x22 height=\x224\ -0px\x22 viewBox=\x220 \ --960 960 960\x22 wi\ -dth=\x2240px\x22 fill=\ -\x22#000000\x22>\ -\x00\x00\x021\ -<\ -svg xmlns=\x22http:\ -//www.w3.org/200\ -0/svg\x22 height=\x224\ -0px\x22 viewBox=\x220 \ --960 960 960\x22 wi\ -dth=\x2240px\x22 fill=\ -\x22#FFFFFF\x22>\ -\ -\x00\x00\x02\xd3\ -<\ -svg xmlns=\x22http:\ -//www.w3.org/200\ -0/svg\x22 height=\x224\ -0px\x22 viewBox=\x220 \ --960 960 960\x22 wi\ -dth=\x2240px\x22 fill=\ -\x22#FFFFFF\x22>\ -\x00\x00\x05L\ -<\ -svg width=\x2240\x22 h\ -eight=\x2240\x22 viewB\ -ox=\x220 0 40 40\x22 f\ -ill=\x22none\x22 xmlns\ -=\x22http://www.w3.\ -org/2000/svg\x22>\x0a<\ -path d=\x22M20.0001\ - 15.1667L13.7222\ - 21.4722H26.3055\ -L20.0001 15.1667\ -ZM20.0001 3.3334\ -1C22.287 3.33341\ - 24.4444 3.77091\ - 26.4722 4.64591\ -C28.4999 5.52091\ - 30.2686 6.713 3\ -1.778 8.22216C33\ -.2872 9.73161 34\ -.4792 11.5002 35\ -.3542 13.528C36.\ -2292 15.5558 36.\ -6667 17.7131 36.\ -6667 20.0001C36.\ -6667 22.3056 36.\ -2292 24.4723 35.\ -3542 26.5001C34.\ -4792 28.5279 33.\ -2872 30.2917 31.\ -778 31.7917C30.2\ -686 33.2917 28.4\ -999 34.4792 26.4\ -722 35.3542C24.4\ -444 36.2292 22.2\ -87 36.6667 20.00\ -01 36.6667C17.69\ -45 36.6667 15.52\ -79 36.2292 13.50\ -01 35.3542C11.47\ -23 34.4792 9.708\ -41 33.2917 8.208\ -41 31.7917C6.708\ -41 30.2917 5.520\ -91 28.5279 4.645\ -91 26.5001C3.770\ -91 24.4723 3.333\ -41 22.3056 3.333\ -41 20.0001C3.333\ -41 17.7131 3.770\ -91 15.5558 4.645\ -91 13.528C5.5209\ -1 11.5002 6.7084\ -1 9.73161 8.2084\ -1 8.22216C9.7084\ -1 6.713 11.4723 \ -5.52091 13.5001 \ -4.64591C15.5279 \ -3.77091 17.6945 \ -3.33341 20.0001 \ -3.33341ZM20.0001\ - 6.11133C16.1298\ - 6.11133 12.8474\ - 7.46313 10.153 \ -10.1667C7.45855 \ -12.8704 6.11133 \ -16.1481 6.11133 \ -20.0001C6.11133 \ -23.8704 7.45855 \ -27.1527 10.153 2\ -9.8472C12.8474 3\ -2.5416 16.1298 3\ -3.8888 20.0001 3\ -3.8888C23.852 33\ -.8888 27.1298 32\ -.5416 29.8334 29\ -.8472C32.537 27.\ -1527 33.8888 23.\ -8704 33.8888 20.\ -0001C33.8888 16.\ -1481 32.537 12.8\ -704 29.8334 10.1\ -667C27.1298 7.46\ -313 23.852 6.111\ -33 20.0001 6.111\ -33Z\x22 fill=\x22black\ -\x22/>\x0a\x0a\ -\x00\x00\x02\xde\ -<\ -svg xmlns=\x22http:\ -//www.w3.org/200\ -0/svg\x22 height=\x224\ -0px\x22 viewBox=\x220 \ --960 960 960\x22 wi\ -dth=\x2240px\x22 fill=\ -\x22#000000\x22>\ -\x00\x00\x021\ -<\ -svg xmlns=\x22http:\ -//www.w3.org/200\ -0/svg\x22 height=\x224\ -0px\x22 viewBox=\x220 \ --960 960 960\x22 wi\ -dth=\x2240px\x22 fill=\ -\x22#000000\x22>\ -\ -\x00\x00\x05L\ -<\ -svg width=\x2240\x22 h\ -eight=\x2240\x22 viewB\ -ox=\x220 0 40 40\x22 f\ -ill=\x22none\x22 xmlns\ -=\x22http://www.w3.\ -org/2000/svg\x22>\x0a<\ -path d=\x22M20.0001\ - 15.1667L13.7222\ - 21.4722H26.3055\ -L20.0001 15.1667\ -ZM20.0001 3.3334\ -1C22.287 3.33341\ - 24.4444 3.77091\ - 26.4722 4.64591\ -C28.4999 5.52091\ - 30.2686 6.713 3\ -1.778 8.22216C33\ -.2872 9.73161 34\ -.4792 11.5002 35\ -.3542 13.528C36.\ -2292 15.5558 36.\ -6667 17.7131 36.\ -6667 20.0001C36.\ -6667 22.3056 36.\ -2292 24.4723 35.\ -3542 26.5001C34.\ -4792 28.5279 33.\ -2872 30.2917 31.\ -778 31.7917C30.2\ -686 33.2917 28.4\ -999 34.4792 26.4\ -722 35.3542C24.4\ -444 36.2292 22.2\ -87 36.6667 20.00\ -01 36.6667C17.69\ -45 36.6667 15.52\ -79 36.2292 13.50\ -01 35.3542C11.47\ -23 34.4792 9.708\ -41 33.2917 8.208\ -41 31.7917C6.708\ -41 30.2917 5.520\ -91 28.5279 4.645\ -91 26.5001C3.770\ -91 24.4723 3.333\ -41 22.3056 3.333\ -41 20.0001C3.333\ -41 17.7131 3.770\ -91 15.5558 4.645\ -91 13.528C5.5209\ -1 11.5002 6.7084\ -1 9.73161 8.2084\ -1 8.22216C9.7084\ -1 6.713 11.4723 \ -5.52091 13.5001 \ -4.64591C15.5279 \ -3.77091 17.6945 \ -3.33341 20.0001 \ -3.33341ZM20.0001\ - 6.11133C16.1298\ - 6.11133 12.8474\ - 7.46313 10.153 \ -10.1667C7.45855 \ -12.8704 6.11133 \ -16.1481 6.11133 \ -20.0001C6.11133 \ -23.8704 7.45855 \ -27.1527 10.153 2\ -9.8472C12.8474 3\ -2.5416 16.1298 3\ -3.8888 20.0001 3\ -3.8888C23.852 33\ -.8888 27.1298 32\ -.5416 29.8334 29\ -.8472C32.537 27.\ -1527 33.8888 23.\ -8704 33.8888 20.\ -0001C33.8888 16.\ -1481 32.537 12.8\ -704 29.8334 10.1\ -667C27.1298 7.46\ -313 23.852 6.111\ -33 20.0001 6.111\ -33Z\x22 fill=\x22white\ -\x22/>\x0a\x0a\ -\x00\x00\x02\xbb\ -<\ -svg xmlns=\x22http:\ -//www.w3.org/200\ -0/svg\x22 height=\x224\ -0px\x22 viewBox=\x220 \ --960 960 960\x22 wi\ -dth=\x2240px\x22 fill=\ -\x22#FFFFFF\x22>\ -\x00\x00\x025\ -<\ -svg xmlns=\x22http:\ -//www.w3.org/200\ -0/svg\x22 height=\x224\ -0px\x22 viewBox=\x220 \ --960 960 960\x22 wi\ -dth=\x2240px\x22 fill=\ -\x22#FFFFFF\x22>\ -" - -qt_resource_name = b"\ -\x00\x07\ -\x0c\xba\xb6s\ -\x00v\ -\x00e\x00c\x00t\x00o\x00r\x00s\ -\x00\x06\ -\x07\xae\xc3\xc3\ -\x00t\ -\x00h\x00e\x00m\x00e\x00s\ -\x00\x0e\ -\x03\x9b1c\ -\x00l\ -\x00i\x00g\x00h\x00t\x00s\x00t\x00y\x00l\x00e\x00.\x00q\x00s\x00s\ -\x00\x0e\ -\x0d\xd6\xeag\ -\x00l\ -\x00o\x00c\x00k\x00_\x00c\x00l\x00o\x00s\x00e\x00.\x00s\x00v\x00g\ -\x00\x13\ -\x0cPg\xa7\ -\x00a\ -\x00r\x00r\x00o\x00w\x00_\x00d\x00o\x00w\x00n\x00_\x00d\x00a\x00r\x00k\x00.\x00s\ -\x00v\x00g\ -\x00\x0a\ -\x0f\xec\x03\xe7\ -\x00d\ -\x00e\x00s\x00i\x00g\x00n\x00.\x00s\x00v\x00g\ -\x00\x12\ -\x0aDDG\ -\x00a\ -\x00r\x00r\x00o\x00w\x00_\x00u\x00p\x00_\x00l\x00i\x00g\x00h\x00t\x00.\x00s\x00v\ -\x00g\ -\x00\x0d\ -\x005w\xc7\ -\x00l\ -\x00o\x00c\x00k\x00_\x00o\x00p\x00e\x00n\x00.\x00s\x00v\x00g\ -\x00\x14\ -\x01\xd3}\xa7\ -\x00a\ -\x00r\x00r\x00o\x00w\x00_\x00d\x00o\x00w\x00n\x00_\x00l\x00i\x00g\x00h\x00t\x00.\ -\x00s\x00v\x00g\ -\x00\x11\ -\x03\xf9t'\ -\x00a\ -\x00r\x00r\x00o\x00w\x00_\x00u\x00p\x00_\x00d\x00a\x00r\x00k\x00.\x00s\x00v\x00g\ -\ -\x00\x11\ -\x08\xb1e\xc7\ -\x00d\ -\x00e\x00s\x00i\x00g\x00n\x00_\x00r\x00e\x00p\x00o\x00r\x00t\x00.\x00s\x00v\x00g\ -\ -\x00\x08\ -\x08\xc8U\xe7\ -\x00s\ -\x00a\x00v\x00e\x00.\x00s\x00v\x00g\ -" - -qt_resource_struct = b"\ -\x00\x00\x00\x00\x00\x02\x00\x00\x00\x02\x00\x00\x00\x01\ -\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x14\x00\x02\x00\x00\x00\x01\x00\x00\x00\x0c\ -\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x02\x00\x00\x00\x09\x00\x00\x00\x03\ -\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\xda\x00\x00\x00\x00\x00\x01\x00\x00#\xaa\ -\x00\x00\x01\x9a\xbaP\xcf{\ -\x00\x00\x00\xfa\x00\x00\x00\x00\x00\x01\x00\x00&\x8c\ -\x00\x00\x01\x9a\xb4\xb2\x08\xc2\ -\x00\x00\x01(\x00\x00\x00\x00\x00\x01\x00\x00(\xc1\ -\x00\x00\x01\x9a\xb4\xbe\x94N\ -\x00\x00\x01P\x00\x00\x00\x00\x00\x01\x00\x00.\x11\ -\x00\x00\x01\x9a\x0f\xf20:\ -\x00\x00\x01x\x00\x00\x00\x00\x00\x01\x00\x000\xd0\ -\x00\x00\x01\x9a\x0f\xf20\xf8\ -\x00\x00\x00\xb0\x00\x00\x00\x00\x00\x01\x00\x00\x1eZ\ -\x00\x00\x01\x9a\xb4\xbe\xd7\xa7\ -\x00\x00\x00j\x00\x00\x00\x00\x00\x01\x00\x00\x19N\ -\x00\x00\x01\x9a\xb4\xb1\xf3c\ -\x00\x00\x00H\x00\x00\x00\x00\x00\x01\x00\x00\x16L\ -\x00\x00\x01\x9a\xbaP\xb8\xcc\ -\x00\x00\x00\x96\x00\x00\x00\x00\x00\x01\x00\x00\x1b\x83\ -\x00\x00\x01\x9a\x0f\xf20\x18\ -\x00\x00\x00&\x00\x01\x00\x00\x00\x01\x00\x00\x00\x00\ -\x00\x00\x01\x9a\xb4\xfd=\x83\ -" - -def qInitResources(): - QtCore.qRegisterResourceData(0x03, qt_resource_struct, qt_resource_name, qt_resource_data) - -def qCleanupResources(): - QtCore.qUnregisterResourceData(0x03, qt_resource_struct, qt_resource_name, qt_resource_data) - -qInitResources() diff --git a/src/osdagbridge/desktop/legacy_src/src/osbridge/resources/themes/darkstyle.qss b/src/osdagbridge/desktop/legacy_src/src/osbridge/resources/themes/darkstyle.qss deleted file mode 100644 index 2e384e1b..00000000 --- a/src/osdagbridge/desktop/legacy_src/src/osbridge/resources/themes/darkstyle.qss +++ /dev/null @@ -1,1646 +0,0 @@ -/* ============================================== - OSDAG GUI STYLESHEET - ORGANIZED BY SPECIFICITY - ============================================== */ - -/* ============================================== - 1. GLOBAL STYLES (Least Specific) - ============================================== */ -* { - font-family: "Ubuntu Sans"; -} - -QMainWindow { - background-color: #282828; - border: 1px solid #6B7D20; - margin: 0px; - padding: 0px; -} - -QTabWidget { - background-color: #333333; - border: 0px; -} - -QToolTip { - background-color: #2B2B2B; - color: #D0D0D0; - border: 1px solid #6B7D20; - padding: 2px 2px; - font-size: 12px; - border-radius: 0px; - qproperty-alignment: AlignVCenter; -} - -/* ============================================== - 2. TRANSPARENT BACKGROUNDS - ============================================== */ -QWidget#search_overlay_scroll_content { - background: transparent; -} -QWidget#svg_card_area, -QWidget#home_top_right_container, -QWidget#mid_top_svg_lay_wrapper, -QWidget#pr_menu_container, -QWidget#svg_widget_background, -QWidget#sec_menu_container, -QFrame#actionsFrame { - background: transparent; -} - -QScrollArea#svgCard_scroll_area{ - background: transparent; - border: none; -} -QScrollArea#search_overlay_scrollarea { - background: transparent; - border: none; - border-radius: 12px; - margin: 3px; -} - -/* ============================================== - 3. GENERAL BUTTON STYLES (Base Level) - ============================================== */ -QPushButton { - background-color: #282828; - color: #D0D0D0; - font-weight: bold; - border-radius: 5px; - border: 1px solid #D0D0D0; - padding: 5px 14px; - text-align: center; -} - -QPushButton:hover { - background-color: #6B7D20; - border: 1px solid #6B7D20; - color: white; -} - -QPushButton:pressed { - color: black; - background-color: #383838; - border: 1px solid #6B7D20; -} - -/* ============================================== - 4. SPECIFIC BUTTON STYLES (Override General) - ============================================== */ - -/* Window Control Buttons */ -QPushButton#window_control_button, -QPushButton#close_button { - background-color: #353535; - color: white; - border-radius: 0px; - border: none; - padding: 0px; -} - -QPushButton#window_control_button:hover { - background-color: #444444; -} - -QPushButton#window_control_button:pressed { - background-color: #454545; -} - -QPushButton#close_button:hover { - background-color: #E81123; -} - -QPushButton#close_button:pressed { - background-color: #F1707A; -} - -/* Theme Toggle Button */ -QPushButton#themeToggle { - background: transparent; - border: none; - border-radius: 20px; -} - -QPushButton#themeToggle:hover { - background-color: rgba(255, 255, 255, 100); -} - -/* Additional Input Button */ -QPushButton#additional_input_btn { - background-color: #282828; - color: #D0D0D0; - font-weight: bold; - border-radius: 5px; - border: 1px solid #D0D0D0; - padding: 5px 14px; - text-align: center; -} - -QPushButton#additional_input_btn:hover { - background-color: #6B7D20; - border: 1px solid #6B7D20; - color: white; -} - -QPushButton#additional_input_btn:pressed { - color: #D0D0D0; - background-color: #282828; - border: 1px solid #D0D0D0; -} - -/* Navbar Button */ -QPushButton#navbar_button { - background-color: #333333; - color: #FFFFFF; - padding: 4px 4px 8px 8px; - font-weight: normal; - border-radius: 0px; - border: none; - border-top: 1px solid #333333; - border-bottom: 1px solid #333333; - text-align: left; -} - -QPushButton#navbar_button:hover { - background-color: transparent; - color: #90AF13; - border-top: 1px solid #6B7D20; - border-bottom: 1px solid #6B7D20; -} - -QPushButton#navbar_button[state="active"] { - background-color: #6B7D20; - color: #ffffff; - border-radius: 0px; - border: none; - padding: 4px 4px 8px 8px; - border-top: 1px solid #6B7D20; - border-bottom: 1px solid #6B7D20; - text-align: left; -} - -/* Menu Button */ -QPushButton#menu_button { - font-size: 14px; - width: 140px; - color: #FFFFFF; - background-color: #282828; - border: 2px solid #E5E5E5; - border-radius: 5px; - padding: 8px 4px; - margin: 1px 2px; -} - -QPushButton#menu_button:hover { - border: 2px solid #6B7D20; -} - -QPushButton#menu_button:pressed { - background-color: #6B7D20; - border: 2px solid #6B7D20; - color: white; -} - -QPushButton#menu_button[selected="true"] { - color: white; - background-color: #6B7D20; - border: 2px solid #6B7D20; -} - -/* Dock Custom Button */ -QPushButton#dock_custom_button { - background-color: #6B7D20; - border: 0px; - border-radius: 5px; - padding: 10px; - text-align: center; -} - -QPushButton#dock_custom_button:pressed { - background-color: #7d9710; -} - -/* Search Result Project Button */ -QPushButton#search_result_proj_btn { - background-color: #FFFFFF; - border-radius: 12px; - color: #2d3748; - border: 1px solid #cccccc; - padding: 4px 5px; - font-size: 10px; - font-weight: 600; -} - -QPushButton#search_result_proj_btn:hover { - background-color: #f0f4e3; - border-color: #9BC53D; -} - -QPushButton#search_result_proj_btn:pressed { - background-color: #d6e6be; -} - -/* ============================================== - 5. NESTED WIDGET STYLES (Most Specific) - ============================================== */ - -/* Dock Custom Button Children */ -QPushButton#dock_custom_button QLabel#button_label { - background: transparent; - color: white; -} - -QPushButton#dock_custom_button QSvgWidget#button_icon { - background: transparent; -} - -/* ============================================== - 6. TAB BAR STYLES - ============================================== */ -QTabBar::tab { - background: #282828; - border-left: 1px solid rgb(121, 115, 115); - border-right: 1px solid rgb(121, 115, 115); - border-top: 1px solid #353535; - border-bottom: 1px solid #353535; - padding: 6px 18px 6px 18px; - color: #FFFFFF; - font-size: 11px; - margin-left: 0px; -} - -QTabBar::tab:selected { - background: #333333; - color: #FFFFFF; - border: 1px solid #90AF13; - border-bottom: 1px solid #333333; - padding: 6px 18px 6px 18px; -} - -QTabBar::tab:hover { - border-top: 1px solid #90AF13; - border-left: 1px solid #90AF13; - border-right: 1px solid #90AF13; -} - -QTabBar::close-button { - image: url(:/vectors/window_close_dark.svg); - subcontrol-origin: padding; - subcontrol-position: center right; -} - -QTabBar::close-button:hover { - image: url(:/vectors/window_close_hover.svg); -} - -/* ============================================== - 7. FRAME STYLES - ============================================== */ -QWidget#BottomLine { - background-color: #6B7D20; -} - -QFrame#navbar_header { - background-color: #333333; -} - -QFrame#overlayFrame { - background-color: #282828; - border: 1px solid #e2e8f0; - border-radius: 15px; -} - -QFrame#SvgCard { - border-radius: 12px; - background-color: #282828; - border: 2px solid #E5E5E5; -} - -QFrame#SvgCard[state="default"] { - border-radius: 12px; - background-color: #282828; - border: 2px solid #E5E5E5; -} - -QFrame#SvgCard[state="selected"] { - background-color: #6B7D20; - border: 2px solid #90AF13; - border-radius: 12px; -} - -QFrame#SvgCard[state="hover"] { - background-color: #6B7D20; - border: 2px solid #90AF13; - border-radius: 12px; -} - -QFrame#search_result_item { - background: #282828; - border: 1px solid #282828; - color: #FFFFFF; - border-radius: 15px; - padding: 4px 12px; -} - -QFrame#search_result_item:hover { - background: #6B7D20; -} - -/* ============================================== - 8. LABEL STYLES - ============================================== */ -QLabel#version_label { - color: gray; -} - -QLabel#module_section_label { - color: #FFFFFF; - font-size: 16px; - background-color: rgba(138, 138, 138, 110); - padding: 2px 0px; - border-top: 1px solid #6B7D20; - border-bottom: 1px solid #6B7D20; -} - -QLabel#svgCard_title { - font-weight: bold; - color: #FFFFFF; - background: transparent; - font-size: 13px; - margin-top: 5px; -} - -QLabel#under_dev_label { - color: #FFFFFF; - font-size: 16px; -} - -QLabel#svgCard_open_label { - background-color: #333333; - color: #FFFFFF; - font-weight: bold; - border-top: 2px solid #6B7D20; - border-bottom-left-radius: 12px; - border-bottom-right-radius: 12px; - padding: 0px; - margin: 0px; -} - -QLabel#svgCard_open_label[state="default"] { - background-color: #333333; - color: #FFFFFF; - font-weight: bold; - border-top: 2px solid #6B7D20; - border-bottom-left-radius: 12px; - border-bottom-right-radius: 12px; - padding: 0px; - margin: 0px; -} - -QLabel#svgCard_open_label:hover { - background-color: #282828; - color: #6B7D20; - font-weight: bold; - border-top: 2px solid #6B7D20; - border-bottom-left-radius: 12px; - border-bottom-right-radius: 12px; - padding: 0px; - margin: 0px; -} - -QLabel#svgCard_open_label[state="selected"] { - background-color: #333333; - color: black; - font-weight: bold; - border-top: 2px solid #6B7D20; - border-bottom-left-radius: 12px; - border-bottom-right-radius: 12px; - padding: 0px; - margin: 0px; -} - -/* Search Overlay Labels */ -QLabel#projectsHeader { - color: #FFFFFF; - font-size: 12px; - font-weight: bold; - padding: 8px 16px; - border-radius: 12px; - background-color: #282828; - border-bottom: 1px solid #E2E8F0; -} - -QLabel#modulesHeader { - color: #FFFFFF; - font-size: 12px; - font-weight: bold; - padding: 8px 16px; - border-radius: 12px; - background-color: #282828; - border-top: 1px solid #E2E8F0; - border-bottom: 1px solid #E2E8F0; -} - -QLabel#noResults { - color: #718096; - font-size: 13px; - padding: 24px; -} - -QLabel#primaryText { - color: #FFFFFF; - font-size: 13px; - font-weight: 600; - background: transparent; -} - -QLabel#secondaryText { - color: #718096; - font-size: 12px; - background: transparent; -} - -QLabel#dateText { - color: #a0aec0; - background: transparent; - font-size: 11px; -} - -QLabel#iconLabel { - color: #FFFFFF; - font-size: 16px; - background: transparent; - font-weight: bold; -} - -/* ============================================== - 9. SCROLLBAR STYLES - ============================================== */ -QScrollArea#search_overlay_scrollarea QScrollBar:vertical { - border: none; - background: #f7fafc; - width: 3px; - margin-top: 8px; - margin-bottom: 8px; -} - -QScrollArea#search_overlay_scrollarea QScrollBar::handle:vertical { - background: #cbd5e0; - border-radius: 2px; - min-height: 20px; -} - -QScrollArea#search_overlay_scrollarea QScrollBar::handle:vertical:hover { - background: #a0aec0; -} - -QScrollArea#search_overlay_scrollarea QScrollBar::add-line:vertical, -QScrollArea#search_overlay_scrollarea QScrollBar::sub-line:vertical { - border: none; - background: none; -} - -QScrollArea#search_overlay_scrollarea QScrollBar::add-page:vertical, -QScrollArea#search_overlay_scrollarea QScrollBar::sub-page:vertical { - background: none; -} - -QScrollArea#recents_scroll_area{ - background: transparent; - border: none; - padding: 10px; -} - -/* Scrollbar itself */ -QScrollArea#recents_scroll_area QScrollBar:vertical { - border: none; - background: #f0f0f0; - width: 8px; - margin: 0px 0px 0px 0px; -} - -/* Handle */ -QScrollArea#recents_scroll_area QScrollBar::handle:vertical { - background: #c0c0c0; - border-radius: 4px; - min-height: 20px; -} - -/* Handle on hover */ -QScrollArea#recents_scroll_area QScrollBar::handle:vertical:hover { - background: #a0a0a0; -} - -/* Handle when pressed */ -QScrollArea#recents_scroll_area QScrollBar::handle:vertical:pressed { - background: #808080; /* Even darker grey when pressed */ -} - -/* Remove add/sub line buttons (arrows) */ -QScrollArea#recents_scroll_area QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical { - border: none; - background: none; -} - -/* Styling the page area (the track around the handle) */ -QScrollArea#recents_scroll_area QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { - background: none; /* Make the page area transparent, letting the QScrollBar background show through */ -} - -/* Default button style */ -QPushButton#TopButton { - background: #282828; - border-radius: 0px; - border: 1px solid #E5E5E5; - color: #FFFFFF; - font-size: 1px; - padding: 10px; - text-align: center; -} - -/* Hover button style */ -QPushButton#TopButton[hover="true"] { - background: #6B7D20; - border: 1px solid #6B7D20; - color: #FFFFFF; - font-size: 14px; - font-weight: 600; - padding: 10px; - text-align: center; -} - -/* Pressed button style */ -QPushButton#TopButton[pressed="true"] { - background: #90AF13; - border: 1px solid #90AF13; - color: #FFFFFF; - font-size: 14px; - font-weight: 600; - padding: 10px; - text-align: center; -} - -/* Dropdown menu style */ -QMenu { - background: #282828; - border: 1px solid #6B7D20; - font-size: 14px; - padding: 0px; -} - -QMenu::item { - padding: 8px 16px; - color: #FFFFFF; - border: none; - margin: 1px; -} - -QMenu::item:selected { - background: #6B7D20; - color: #FFFFFF; - border-radius: 2px; -} - -QWidget#searchContainer { - border: 2px solid #E5E5E5; - border-radius: 24px; - background: #282828; - color: #FFFFFF; - min-height: 48px; - padding: 0px 8px; -} -QWidget#searchContainer[focused="true"] { - border-color: #6B7D20; -} -QLineEdit#searchInput { - border: none; - background: transparent; - font-size: 16px; - color: #FFFFFF; -} -QLineEdit#searchInput:focus { - outline: none; - border: none; -} -QLabel#shortcutKey, #lKey { - background: #333333; - border: 1px solid #333333; - border-radius: 4px; - color: #FFFFFF; - font-size: 11px; - font-weight: 600; - padding: 2px 4px; -} -QLabel#plusLabel { - color: #FFFFFF; - font-size: 12px; - font-weight: 500; -} - -QFrame#projectItem{ - background: #282828; - border: 1px solid #E2E8F0; - margin: 2px 4px 6px 4px; - border-radius: 10px; -} -QFrame#projectItem:hover{ - background: #6B7D20; - border-color: #6B7D20; -} -QFrame#projectItem[selected="true"] { - background: #6B7D20; - border-color: #6B7D20; -} -QLabel#projectName{ - color: #FFFFFF; - font-size: 13px; -} -QLabel#submoduleName, -QLabel#dateLabel { - color: #E2E8F0; - font-size: 11px; -} - -QLabel#record_icon_label { - font-weight: bold; - color: #FFFFFF; - font-size: 16px; -} - -QPushButton#recent_proj_btn { - background-color: #ffffff ; - border-radius: 12px; - color: #2d3748 ; - border: 1px solid #cccccc ; - padding: 4px 5px ; - font-size: 10px ; - font-weight: 600 ; -} -QPushButton#recent_proj_btn:hover { - background-color: #f0f4e3 ; - border-color: #9BC53D ; -} -QPushButton#recent_proj_btn:pressed { - background-color: #d6e6be ; -} - -QFrame#moduleItem { - background: #282828; - border: 1px solid #E2E8F0; - margin: 2px 4px 6px 4px; - border-radius: 10px; -} -QFrame#moduleItem:hover { - background: #6B7D20; - border-color: #6B7D20; -} -QLabel#moduleName { - color: #FFFFFF; - font-size: 13px; -} -QLabel#subModuleLabel, QLabel#dateLabel { - color: #E2E8F0; - font-size: 11px; -} - -QFrame#sectionFrame { - background: #282828; - border: 2px solid #e1e5e9; - border-radius: 18px; -} -QLabel#sectionHeading { - font-family: "Calibri", sans-serif; - font-size: 18px; - font-weight: bold; - color: #FFFFFF; - background: transparent; - padding: 8px; -} -QWidget#scrollContainer { - background: transparent; - border: none; - margin: 2px 2px 6px 2px; -} -QWidget#scrollContainer_empty { - background: #333333; - border: 1px solid #333333; - margin: 2px 2px 6px 2px; - border-radius: 10px; -} -QWidget#SplashScreen_CentralWidget { - border: 2px solid #90af13; - border-radius: 5px; - background-image: url(':/backgrounds/splash_bg.jpg'); - background-repeat: no-repeat; - background-position: center; -} -QLabel#splash_loading_label { - background-color: rgba(255,255,255,0.3); -} - -/*=============================================================== - cad view buttons -===============================================================*/ -QPushButton#cad_view_buttons { - background-color: #333333; - border: 0px; - border-radius: 0; - color: #D0D0D0; - padding: 0; - font-weight: bold; -} -QPushButton#cad_view_buttons:hover { - background-color: #6B7D20; - border: 1px solid #6B7D20; - color: white; -} -QPushButton#cad_view_buttons:pressed { - background-color: #333333; - border: 1px solid #6B7D20; - color: black; -} -/*===============================================================*/ - -QWidget#output_dock_indicator, -QWidget#input_dock_indicator { - background-color: #333333; -} - -/*=======================Template-Page===========================*/ - -QWidget#template_page { - background-color: #333333; - margin: 0px; - padding: 0px; -} -QWidget#control_btn_widget { - background-color: #383838; - padding: 0px; -} -QMenuBar#template_page_menu_bar { - background-color: #383838; - color:rgb(234, 234, 234); - padding: 0px; -} -QMenuBar#template_page_menu_bar::item { - padding: 5px 10px; - background: transparent; - border-radius: 0px; -} -QMenuBar#template_page_menu_bar::item:selected { - background: #282828; -} -QMenuBar#template_page_menu_bar::item:pressed { - background: rgb(94, 94, 94); -} -QMenuBar#template_page_menu_bar QMenu { - background-color: #383838; - border: 1px solid #D0D0D0; - border-radius: 4px; - padding: 0px; -} -QMenuBar#template_page_menu_bar QMenu::item { - padding: 5px; - color: #D0D0D0; - font-size: 11px; -} -QMenuBar#template_page_menu_bar QMenu::item:selected { - background-color:rgb(94, 94, 94); - border-radius: 3px; -} -QMenuBar#template_page_menu_bar QMenu::separator { - height: 1px; - background: #D0D0D0; - margin-left: 2px; - margin-right: 2px; - margin-top: 0px; - margin-bottom: 0px; -} -QMenuBar#template_page_menu_bar QMenu::right-arrow { - width: 8px; - height: 8px; -} -QWidget#toggle_strip { - background-color: #94b816; -} -QPushButton#toggle_strip_button { - background-color: #6B7D20; - color: white; - font-size: 12px; - font-weight: bold; - padding: 0px; - border: none; -} -QPushButton#toggle_strip_button:hover { - background-color: #5e7407; -} - -QWidget#cad_custom_selector { - padding: 5px; -} -QWidget#cad_custom_selector QCheckBox { - color: #FFFFFF; - margin: 5px; - border-style: solid; - border-width: 1px; - border-color: transparent; -} -QWidget#cad_custom_selector QCheckBox:disabled { - color: #808080; - margin: 5px; - border-style: solid; - border-width: 1px; - border-color: transparent; -} -QWidget#cad_custom_selector QCheckBox:hover { - border-radius: 4px; - border-style: solid; - border-width: 1px; - border-color: transparent; -} - -QWidget#cad_custom_selector QCheckBox::indicator { - width: 12px; - height: 12px; -} - -QWidget#cad_custom_selector QCheckBox::indicator:checked { - border-style: solid; - border-width: 1px; - border-color: qlineargradient( - spread:pad, x1:0.5, y1:1, x2:0.5, y2:0, - stop:0 rgba(120, 120, 120, 255), - stop:1 rgba(180, 180, 180, 255) - ); - background-color: qlineargradient( - spread:pad, x1:0.5, y1:1, x2:0.5, y2:0, - stop:0 rgba(120, 120, 120, 255), - stop:1 rgba(200, 200, 200, 255) - ); -} - -QWidget#cad_custom_selector QCheckBox::indicator:unchecked { - border-style: solid; - border-width: 1px; - border-color: qlineargradient( - spread:pad, x1:0.5, y1:1, x2:0.5, y2:0, - stop:0 rgba(160, 160, 160, 255), - stop:1 rgba(200, 200, 200, 255) - ); - background-color: #D0D0D0; -} -/*==========floating-navbar=================================*/ -QWidget#floating_btn_container { - background-color: #282828; - border: 1px solid #6B7D20; - border-top-left-radius: 5px; - border-top-right-radius: 5px; - border-bottom-left-radius: 5px; - border-bottom-right-radius: 5px; -} -QPushButton#floating_btn[state="default"] { - background-color: #282828; - border: 0px; - border-radius: 5px; - margin: 3px; - padding: 1px; -} -QPushButton#floating_btn[state="selected"] { - background-color: #6B7D20; - border-radius: 5px; - border: 0px; - margin: 3px; - padding: 1px; -} -QPushButton#floating_btn[state="active"] { - background-color: #6B7D20; - border-radius: 5px; - border: 0px; - margin: 3px; - padding: 1px; -} -/*=======================Input-Dock===========================*/ -QWidget#dock_splitter { - background-color: red; -} - -QWidget #inputs-leftpanel { - background-color: #333333; -} - -QWidget #outputs-rightpanel { - background-color: #333333; -} - -QScrollArea#inputs_vscrollarea, -QScrollArea#outputs_vscrollarea { - border: 1px solid #A0A0A0; - background-color: transparent; - padding: 3px; -} -QScrollArea#inputs_vscrollarea QScrollBar:vertical, -QScrollArea#outputs_vscrollarea QScrollBar:vertical { - background: #383838; - width: 8px; - margin: 0px 0px 0px 3px; - border-radius: 2px; -} -QScrollArea#inputs_vscrollarea QScrollBar::handle:vertical, -QScrollArea#outputs_vscrollarea QScrollBar::handle:vertical { - background: #A0A0A0; - min-height: 30px; - border-radius: 2px; -} -QScrollArea#inputs_vscrollarea QScrollBar::handle:vertical:hover, -QScrollArea#outputs_vscrollarea QScrollBar::handle:vertical:hover { - background: #707070; -} -QScrollArea#inputs_vscrollarea QScrollBar::add-line:vertical, -QScrollArea#inputs_vscrollarea QScrollBar::sub-line:vertical, -QScrollArea#outputs_vscrollarea QScrollBar::add-line:vertical, -QScrollArea#outputs_vscrollarea QScrollBar::sub-line:vertical { - height: 0px; -} -QScrollArea#inputs_vscrollarea QScrollBar::add-page:vertical, -QScrollArea#inputs_vscrollarea QScrollBar::sub-page:vertical, -QScrollArea#outputs_vscrollarea QScrollBar::add-page:vertical, -QScrollArea#outputs_vscrollarea QScrollBar::sub-page:vertical { - background: none; -} - -#inputs_container{ - background-color: #333333; - color: #D0D0D0; -} -#inputs_container QLabel{ - background: transparent; - color: #D0D0D0; -} -#inputs_container QComboBox{ - padding: 2px; - border: 1px solid #D0D0D0; - border-radius: 5px; - background-color: #333333; - color: #D0D0D0; -} -#inputs_container QComboBox::drop-down{ - subcontrol-origin: padding; - subcontrol-position: top right; - border-left: 0px; -} -#inputs_container QComboBox::down-arrow{ - image: url(:/images/down_arrow_dark.png); - width: 15px; - height: 15px; - margin-right: 5px; -} -#inputs_container QComboBox QAbstractItemView{ - background-color: #333333; - border: 1px solid #D0D0D0; - outline: none; -} -#inputs_container QComboBox QAbstractItemView::item{ - color: #D0D0D0; - background-color: #333333; - border: none; - border: 1px solid #333333; - border-radius: 0; - padding: 2px; -} -#inputs_container QComboBox QAbstractItemView::item:hover{ - border: 1px solid #6B7D20; - background-color: #6B7D20; - color: black; -} -#inputs_container QComboBox QAbstractItemView::item:selected{ - background-color: #6B7D20; - color: black; - border: 1px solid #6B7D20; -} -#inputs_container QComboBox QAbstractItemView::item:selected:hover{ - background-color: #6B7D20; - color: black; - border: 1px solid #6B7D20; -} -#inputs_container QLineEdit { - padding: 1px 7px; - border: 1px solid #D0D0D0; - border-radius: 6px; - background-color: #333333; - color: #D0D0D0; - font-weight: normal; -} -#inputs_container QGroupBox{ - border: 1px solid #6B7D20; - color: #D0D0D0; - border-radius: 4px; - margin-top: 0.8em; - font-weight: bold; -} -#inputs_container QGroupBox::title{ - subcontrol-origin: content; - subcontrol-position: top left; - left: 10px; - padding: 0 4px; - margin-top: -12px; - background-color: #333333; -} - -/*=======================Output-Dock===========================*/ - -QWidget#outputs_group_container, -QWidget#outputs_container{ - background-color: #333333; -} -#outputs_container QLabel{ - background: transparent; - color: #D0D0D0; -} -#outputs_container QLineEdit { - padding: 2px 7px; - border: 1px solid #D0D0D0; - border-radius: 4px; - background-color: #333333; - color: #D0D0D0; - font-weight: normal; -} -#outputs_container QGroupBox { - border: 1px solid #6B7D20; - color: #D0D0D0; - border-radius: 4px; - margin-top: 0.8em; - font-weight: bold; -} -#outputs_container QGroupBox::title { - subcontrol-origin: content; - subcontrol-position: top left; - left: 10px; - padding: 0 4px; - margin-top: -12px; - background-color: #333333; -} -#outputs_container QPushButton { - padding: 3px 9px; - background-color: #888888; - color: white; - border: 0px; - border-radius: 4px; - min-width: 100px; - max-width: 120px; - font-size: 12px; -} -#outputs_container QPushButton:disabled { - background-color: #cccccc; - color: #888888; -} - -QPushButton#inputs_button, -QPushButton#outputs_button { - background-color: #6B7D20; - border: 0px; - color: white; - font-weight: bold; - border-radius: 4px; - padding: 7px 14px; -} -QDialog#custom_material_popup { - background-color: #333333; - color: #FFFFFF; - border: 1px solid #90AF13; -} -#custom_material_popup QLabel { - color: #ffffff; -} -#custom_material_popup QLineEdit { - background-color: #333333; - color: #FFFFFF; - border: 1px solid #D0D0D0; - border-radius: 3px; - padding: 2px 4px; -} -#custom_material_popup QLineEdit[readOnly="true"] { - background-color: #282828; -} -#custom_material_popup QLineEdit:focus { - border: 1px solid #90af13; -} -#custom_material_popup QPushButton { - background-color: #90af13; - color: #ffffff; - padding: 6px 16px; - border-radius: 3px; - border: none; - font-weight: bold; -} -#custom_material_popup QPushButton:hover { - background-color: #7a9a12; -} -#custom_material_popup QPushButton:pressed { - background-color: #5f7a0e; -} - -QScrollArea#inputs_hscrollarea, -QScrollArea#outputs_hscrollarea { - border: none; - background: transparent; -} -QScrollArea#inputs_hscrollarea QScrollBar:horizontal, -QScrollArea#outputs_hscrollarea QScrollBar:horizontal { - background: #E0E0E0; - height: 8px; - margin: 3px 0px 0px 0px; - border-radius: 2px; -} -QScrollArea#inputs_hscrollarea QScrollBar::handle:horizontal, -QScrollArea#outputs_hscrollarea QScrollBar::handle:horizontal { - background: #A0A0A0; - min-width: 30px; - border-radius: 2px; -} -QScrollArea#inputs_hscrollarea QScrollBar::handle:horizontal:hover, -QScrollArea#outputs_hscrollarea QScrollBar::handle:horizontal:hover { - background: #707070; -} -QScrollArea#inputs_hscrollarea QScrollBar::add-line:horizontal, QScrollBar::sub-line:horizontal, -QScrollArea#outputs_hscrollarea QScrollBar::add-line:horizontal, QScrollBar::sub-line:horizontal { - width: 0px; -} -QScrollArea#inputs_hscrollarea QScrollBar::add-page:horizontal, QScrollBar::sub-page:horizontal, -QScrollArea#outputs_hscrollarea QScrollBar::add-page:horizontal, QScrollBar::sub-page:horizontal { - background: none; -} - -/*=======================Logs-Dock===========================*/ -QWidget#logs_dock QLabel { - background-color: #333333; - color: #D0D0D0; - padding: 3px; - font-weight: bold; - font-size: 12px; -} -QWidget#logs_dock QTextEdit { - background-color: #333333; - border: 1px solid #D0D0D0; - font-family: monospace; - font-size: 12px; - padding: 5px; - color: #D0D0D0; -} -QWidget#logs_dock QScrollBar:vertical { - background: #E0E0E0; /* Light grey for the scrollbar track */ - width: 8px; - margin: 0px 0px 0px 3px; - border-radius: 2px; -} -QWidget#logs_dock QScrollBar::handle:vertical { - background: #A0A0A0; /* Medium grey for the scrollbar handle */ - min-height: 30px; - border-radius: 2px; -} -QWidget#logs_dock QScrollBar::handle:vertical:hover { - background: #707070; /* Darker grey on hover for the handle */ -} -QWidget#logs_dock QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical { - height: 0px; /* Hides the up/down arrows */ -} -QWidget#logs_dock QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { - background: none; /* Hides the area between handle and arrows */ -} - -/*=======================dialogs===========================*/ -QDialog#spacing_capacity_details QScrollArea, -QDialog#spacing_capacity_details #spacing_scroll_widget, -QDialog#spacing_dialog QScrollArea, -QDialog#spacing_dialog QWidget#scroll_widget{ - background-color: #333333; -} -QDialog#TutorialsDialog, -QDialog#AskQuestions, -QDialog#AboutOsdagDialog, -QDialog#UpdateDialog, -QDialog#CustomDialog, -QDialog#design_report_popup, -QDialog#spacing_capacity_details, -QDialog#customized_input, -QDialog#spacing_dialog { - background-color: #333333; - border: 1px solid #6B7D20; -} -QWidget#CustomTitleBar { - background-color: #282828; -} -QWidget#BottomLine { - background-color: #6B7D20; -} -QLabel#message_lbl1 { - font-size: 14px; - font-weight: bold; - color: #D0D0D0; -} -QLabel#message_lbl2 { - font-size: 12px; - color: #D0D0D0; -} -QLabel#TitleLabel { - color: #D0D0D0; - padding: 0px; - background: transparent; -} - -QLabel#LogoLabel { - background: transparent; - color: #D0D0D0; - font-size: 14px; -} - -QToolButton#MinimizeButton { - background-color: transparent; - color: #FFFFFF; - border: 0px; - border-radius: 0px; - font-size: 16px; - border-radius: 0px; -} - -QToolButton#MinimizeButton:hover { - background-color: #444444; -} - -QToolButton#MinimizeButton:pressed { - background-color: #454545; -} - -QToolButton#MaxRestoreButton { - background-color: transparent; - color: #FFFFFF; - border: 0px; - border-radius: 0px; - font-size: 16px; - border-radius: 0px; -} - -QToolButton#MaxRestoreButton:hover { - background-color: #444444; -} - -QToolButton#MaxRestoreButton:pressed { - background-color: #454545; -} - -QToolButton#CloseButton { - background-color: transparent; - color: #FFFFFF; - border: 0px; - border-radius: 0px; - font-size: 16px; - border-radius: 0px; -} - -QToolButton#CloseButton:hover { - background-color: #e74c3c; - color: #ffffff; -} - -QToolButton#CloseButton:pressed { - background-color: #c0392b; -} - -QDialog#AboutOsdagDialog QWidget#ContentWidget, -QDialog#UpdateDialog QWidget#ContentWidget, -QDialog#customized_input QWidget#ContentWidget { - background-color: #333333; -} -QDialog#AboutOsdagDialog QTextBrowser, -QDialog#UpdateDialog QTextBrowser { - background-color: #333333; - border: 1px solid #D0D0D0; - border-radius: 4px; - font-family: 'Arial', sans-serif; - font-size: 8pt; - padding: 8px; -} -QDialog#AboutOsdagDialog QPushButton, -QDialog#UpdateDialog QPushButton { - background-color: #6B7D20; - color: white; - border: none; - border-radius: 5px; - padding: 5px 20px; - font-size: 12px; - font-weight: bold; -} -QDialog#AboutOsdagDialog QPushButton:hover, -QDialog#UpdateDialog QPushButton:hover { - background-color: #7A9611; -} -QDialog#AboutOsdagDialog QPushButton:pressed, -QDialog#UpdateDialog QPushButton:pressed { - background-color: #6B850F; -} -QDialog#design_report_popup QPushButton, -QDialog#spacing_capacity_details QPushButton{ - background-color: #333333; - color: #D0D0D0; - font-weight: bold; - border-radius: 5px; - border: 1px solid #D0D0D0; - padding: 5px 14px; - text-align: center; -} -QDialog#design_report_popup QPushButton:hover, -QDialog#spacing_capacity_details QPushButton:hover{ - background-color: #6B7D20; - border: 1px solid #6B7D20; - color: #D0D0D0; -} -QDialog#design_report_popup QPushButton:pressed, -QDialog#spacing_capacity_details QPushButton:pressed{ - color: #D0D0D0; - background-color: #333333; - border: 1px solid #D0D0D0; -} -QDialog#design_report_popup QLineEdit, -QDialog#design_report_popup QTextEdit, -QDialog#spacing_dialog QLineEdit { - padding: 2px 7px; - border: 1px solid #D0D0D0; - border-radius: 4px; - background-color: #333333; - color: #D0D0D0; - font-weight: normal; -} -QDialog#design_report_popup QLabel, -QDialog#spacing_capacity_details QLabel, -QDialog#spacing_dialog QLabel { - background: transparent; - color: #D0D0D0; -} - -QWidget#report_customization_widget QTreeWidget { - border: 1px solid #90AF13; - border-radius: 6px; - background-color: #333333; - show-decoration-selected: 1; -} -QWidget#report_customization_widget QTreeWidget::item { - padding: 4px 6px; - background-color: #333333; - color: #FFFFFF; - border-bottom: 1px solid #D0D0D0; -} -QWidget#report_customization_widget QTreeWidget::item:selected { - background-color: #363d18; - color: #FFFFFF; -} -QWidget#report_customization_widget QTreeWidget::item:hover { - background-color: #363d18; -} -QWidget#report_customization_widget QTreeWidget::indicator { - width: 14px; - height: 14px; -} -QWidget#report_customization_widget QTreeWidget::indicator:unchecked { - border: 1px solid #D0D0D0; - background: #333333; -} -QWidget#report_customization_widget QTreeWidget::indicator:checked { - border: 1px solid #555555; - background: #90AF13; - image: none; -} -QWidget#report_customization_widget QHeaderView::section { - background-color: #282828; - color: #FFFFFF; - padding-left: 5px; - border-radius: 0px; - border-top-left-radius: 6px; - border-top-right-radius: 6px; -} -QWidget#report_customization_widget QTreeWidget QHeaderView { - border-top-left-radius: 6px; - border-top-right-radius: 6px; -} -/*=============customized-inputs======================*/ -QDialog#customized_input QLabel#label, -QDialog#customized_input QLabel#label_2 { - font-weight: bold; - font-size: 13px; - color: #FFFFFF; - padding: 2px; -} -QDialog#customized_input QLabel#note_label { - background-color: #282828; - border: 1px solid #e0e0e0; - border-radius: 5px; - padding: 10px; - color: #FFFFFF; - font-size: 12px; -} -QDialog#customized_input QListWidget { - background-color: #333333; - border: 1px solid #E0E0E0; - border-radius: 5px; - padding: 5px; -} -QDialog#customized_input QListWidget::item { - border-left: 2px solid transparent; - background-color: transparent; - color: #FFFFFF; - padding: 3px; - margin: 1px; -} -QDialog#customized_input QListWidget::item:hover { - background-color: #363d18; - border-left-color: #90af13; -} -QDialog#customized_input QListWidget::item:selected { - background-color: #434b20; - color: black; - border-left: 2px solid #90AF13; -} -QDialog#customized_input QListWidget::item:selected:hover { - background-color: #363d18; - border-left-color: #7a9a12; -} -QDialog#customized_input QListWidget::item:disabled { - color: #FFFFFF; - background-color: #999999; -} -QDialog#customized_input QScrollBar:vertical { - background: #f0f0f0; - width: 5px; - margin: 0; - border-radius: 5px; -} -QDialog#customized_input QScrollBar::handle:vertical { - background: #c0c0c0; - min-height: 30px; - border-radius: 5px; -} -QDialog#customized_input QScrollBar::handle:vertical:hover { - background: #90AF13; -} -QDialog#customized_input QPushButton#pushButton, -QDialog#customized_input QPushButton#pushButton_2, -QDialog#customized_input QPushButton#pushButton_3, -QDialog#customized_input QPushButton#pushButton_4, -QDialog#customized_input QPushButton#pushButton_5 { - background-color: #90AF13; - color: white; - font-weight: bold; - font-size: 14px; - border-radius: 5px; - border: none; - padding: 5px; -} -QDialog#customized_input QPushButton#pushButton:hover, -QDialog#customized_input QPushButton#pushButton_2:hover, -QDialog#customized_input QPushButton#pushButton_3:hover, -QDialog#customized_input QPushButton#pushButton_4:hover, -QDialog#customized_input QPushButton#pushButton_5:hover { - background-color: #7a9a12; -} -QDialog#customized_input QPushButton#pushButton:pressed, -QDialog#customized_input QPushButton#pushButton_2:pressed, -QDialog#customized_input QPushButton#pushButton_3:pressed, -QDialog#customized_input QPushButton#pushButton_4:pressed, -QDialog#customized_input QPushButton#pushButton_5:pressed { - background-color: #5f7a0e; -} -QDialog#customized_input QPushButton#pushButton:disabled, -QDialog#customized_input QPushButton#pushButton_2:disabled, -QDialog#customized_input QPushButton#pushButton_3:disabled, -QDialog#customized_input QPushButton#pushButton_4:disabled, -QDialog#customized_input QPushButton#pushButton_5:disabled { - background-color: #d0d0d0; - color: #888888; -} -/*=======================Additional-Inputs=======================================*/ -QDialog#AdditionalInputs{ - background-color: #333333; - border: 1px solid #6B7D20; -} -QWidget#TableWidget QTabWidget QWidget{ - background-color: #333333; -} -QWidget#TableWidget QTabWidget > QWidget > QWidget > QWidget{ - background-color: #333333; - border: 1px solid #D0D0D0; -} -#AdditionalInputs QTabBar::tab { - background: #282828; - border: 1px solid #D0D0D0; - padding: 6px 18px 6px 18px; - color: #D0D0D0; - margin-left: 0px; -} -#AdditionalInputs QTabBar::tab:selected { - background: #333333; - color: #FFFFFF; - border: 1px solid #D0D0D0; - border-bottom: 1px solid #333333; - padding: 6px 18px 6px 18px; -} -#AdditionalInputs QTabBar::tab:hover { - border-bottom: 1px solid #333333; -} -/* Additional Input Button */ -QDialog#AdditionalInputs QPushButton { - background-color: #282828; - color: #D0D0D0; - font-weight: bold; - border-radius: 5px; - border: 1px solid #D0D0D0; - padding: 5px 14px; - text-align: center; -} -QDialog#AdditionalInputs QPushButton:hover { - background-color: #6B7D20; - border: 1px solid #6B7D20; - color: white; -} -QDialog#AdditionalInputs QPushButton:pressed { - color: #D0D0D0; - background-color: #282828; - border: 1px solid #D0D0D0; -} -QDialog#AdditionalInputs QLineEdit { - padding: 2px 7px; - border: 1px solid #D0D0D0; - border-radius: 4px; - background-color: #333333; - color: #D0D0D0; - font-weight: normal; -} -QDialog#AdditionalInputs QLabel { - color: #D0D0D0; -} -QDialog#AdditionalInputs QComboBox{ - padding: 2px 7px; - border: 1px solid #D0D0D0; - border-radius: 4px; - background-color: white; - color: #D0D0D0; -} -QDialog#AdditionalInputs QComboBox::drop-down{ - subcontrol-origin: padding; - subcontrol-position: top right; - border-left: 0px; -} -QDialog#AdditionalInputs QComboBox::down-arrow{ - image: url(:/images/down_arrow_light.png); - width: 15px; - height: 15px; - margin-right: 5px; -} -QDialog#AdditionalInputs QComboBox QAbstractItemView{ - background-color: #282828; - border: 1px solid #D0D0D0; - outline: none; -} -QDialog#AdditionalInputs QComboBox QAbstractItemView::item{ - color: #D0D0D0; - background-color: #282828; - border: none; - border: 1px solid #282828; - border-radius: 0; - padding: 2px; -} -QDialog#AdditionalInputs QComboBox QAbstractItemView::item:hover{ - border: 1px solid #6B7D20; - background-color: #6B7D20; - color: #D0D0D0; -} -QDialog#AdditionalInputs QComboBox QAbstractItemView::item:selected{ - background-color: #6B7D20; - color: #D0D0D0; - border: 1px solid #6B7D20; -} -QDialog#AdditionalInputs QComboBox QAbstractItemView::item:selected:hover{ - background-color: #6B7D20; - color: #D0D0D0; - border: 1px solid #6B7D20; -} -/* Plate-Girder Bounds Dialog */ -QDialog#BoundsSelectorDialog { - background-color: #333333; - border: 1px solid #6B7D20; - border-radius: 0px; -} -QDialog#BoundsSelectorDialog QLineEdit { - background-color: #333333; - border: 1px solid #D0D0D0; - border-radius: 4px; - padding: 5px 8px; - font-size: 12px; - color: #D0D0D0; -} -QDialog#BoundsSelectorDialog QLineEdit:hover { - border: 1px solid #999999; -} -QDialog#BoundsSelectorDialog QLineEdit:focus { - border: 1px solid #90AF13; - background-color: rgb(51, 51, 51); -} -QDialog#BoundsSelectorDialog QLabel { - color: #FFFFFF; -} \ No newline at end of file diff --git a/src/osdagbridge/desktop/legacy_src/src/osbridge/resources/themes/lightstyle.qss b/src/osdagbridge/desktop/legacy_src/src/osbridge/resources/themes/lightstyle.qss deleted file mode 100644 index bfe319ef..00000000 --- a/src/osdagbridge/desktop/legacy_src/src/osbridge/resources/themes/lightstyle.qss +++ /dev/null @@ -1,1607 +0,0 @@ -/* ============================================== - OSDAG GUI STYLESHEET - ORGANIZED BY SPECIFICITY - ============================================== */ - -/* ============================================== - 1. GLOBAL STYLES (Least Specific) - ============================================== */ -* { - font-family: "Ubuntu Sans"; -} -QMainWindow { - background-color: #f4f4f4; - border: 1px solid #90af13; - margin: 0px; - padding: 0px; -} - -QToolTip { - background-color: #FFFFFF; - color: #000000; - border: 1px solid #90AF13; - padding: 2px 2px; - font-size: 12px; - border-radius: 0px; - qproperty-alignment: AlignVCenter; -} -QSplitter::handle { - background-color: #D0D0D0; -} - -/* ============================================== - 2. TRANSPARENT BACKGROUNDS - ============================================== */ -QWidget#search_overlay_scroll_content, -QWidget#svg_card_area, -QWidget#input_dock, -QWidget#output_dock -QWidget#home_top_right_container, -QWidget#mid_top_svg_lay_wrapper, -QWidget#pr_menu_container, -QWidget#svg_widget_background, -QWidget#sec_menu_container, -QFrame#actionsFrame { - background: transparent; -} - -QScrollArea#svgCard_scroll_area{ - background: transparent; - border: none; -} -QScrollArea#search_overlay_scrollarea { - background: transparent; - border: none; - border-radius: 12px; - margin: 3px; -} -/* ============================================== - 3. GENERAL BUTTON STYLES (Base Level) - ============================================== */ -QPushButton { - background-color: white; - color: black; - font-weight: bold; - border-radius: 5px; - border: 1px solid black; - padding: 5px 14px; - text-align: center; -} - -QPushButton:hover { - background-color: #90AF13; - border: 1px solid #90AF13; - color: white; -} - -QPushButton:pressed { - color: black; - background-color: white; - border: 1px solid black; -} - -/* ============================================== - 4. SPECIFIC BUTTON STYLES (Override General) - ============================================== */ - -/* Window Control Buttons */ -QPushButton#window_control_button, -QPushButton#close_button { - background-color: #f4f4f4; - color: white; - border-radius: 0px; - border: none; - padding: 0px; -} - -QPushButton#window_control_button:hover { - background-color: #d9d7d7; -} - -QPushButton#window_control_button:pressed { - background-color: #cfcfcf; -} - -QPushButton#close_button:hover { - background-color: #E81123; -} - -QPushButton#close_button:pressed { - background-color: #F1707A; -} - -/* Theme Toggle Button */ -QPushButton#themeToggle { - background: transparent; - border: none; - border-radius: 20px; -} - -QPushButton#themeToggle:hover { - background-color: rgba(255, 255, 255, 100); -} - -/* Navbar Button */ -QPushButton#navbar_button { - background-color: #ffffff; - color: #000000; - padding: 4px 4px 8px 8px; - font-weight: normal; - border-radius: 0px; - border: none; - border-top: 1px solid #ffffff; - border-bottom: 1px solid #ffffff; - text-align: left; -} - -QPushButton#navbar_button:hover { - background-color: transparent; - color: #90AF13; - border-top: 1px solid #90AF13; - border-bottom: 1px solid #90AF13; -} - -QPushButton#navbar_button[state="active"] { - background-color: #90AF13; - color: #ffffff; - border-radius: 0px; - border: none; - padding: 4px 4px 8px 8px; - border-top: 1px solid #90AF13; - border-bottom: 1px solid #90AF13; - text-align: left; -} - -/* Menu Button */ -QPushButton#menu_button { - font-size: 14px; - width: 140px; - color: black; - background-color: white; - border: 2px solid #E5E5E5; - border-radius: 5px; - padding: 8px 4px; - margin: 1px 2px; -} - -QPushButton#menu_button:hover { - border: 2px solid #90AF13; -} - -QPushButton#menu_button:pressed { - background-color: #90AF13; - border: 2px solid #90AF13; - color: white; -} - -QPushButton#menu_button[selected="true"] { - color: white; - background-color: #90AF13; - border: 2px solid #90AF13; -} - -/* Dock Custom Button */ -QPushButton#dock_custom_button { - background-color: #90AF13; - border: 0px; - border-radius: 5px; - padding: 10px; - text-align: center; -} - -QPushButton#dock_custom_button:pressed { - background-color: #7d9710; -} - -/* Search Result Project Button */ -QPushButton#search_result_proj_btn { - background-color: #ffffff; - border-radius: 12px; - color: #2d3748; - border: 1px solid #cccccc; - padding: 4px 5px; - font-size: 10px; - font-weight: 600; -} - -QPushButton#search_result_proj_btn:hover { - background-color: #f0f4e3; - border-color: #9BC53D; -} - -QPushButton#search_result_proj_btn:pressed { - background-color: #d6e6be; -} - -/* ============================================== - 5. NESTED WIDGET STYLES (Most Specific) - ============================================== */ - -/* Dock Custom Button Children */ -QPushButton#dock_custom_button QLabel#button_label { - background: transparent; - color: white; -} - -QPushButton#dock_custom_button QSvgWidget#button_icon { - background: transparent; -} - -/* ============================================== - 6. TAB BAR STYLES - ============================================== */ -QTabBar#main_tabs::tab { - background: #F4F4F4; - border-left: 1px solid #d9d7d7; - border-right: 1px solid #d9d7d7; - border-top: 1px solid #F4F4F4; - border-bottom: 1px solid #F4F4F4; - padding: 6px 18px 6px 18px; - color: #000000; - font-size: 11px; - margin-left: 0px; -} - -QTabBar#main_tabs::tab:selected { - background: #ffffff; - color: #000000; - border: 1px solid #90AF13; - border-bottom: 1px solid #ffffff; - padding: 6px 18px 6px 18px; -} - -QTabBar#main_tabs::tab:hover { - border-top: 1px solid #90AF13; - border-left: 1px solid #90AF13; - border-right: 1px solid #90AF13; -} - -QTabBar#main_tabs::close-button { - image: url(:/vectors/window_close_light.svg); - subcontrol-origin: padding; - subcontrol-position: center right; -} - -QTabBar#main_tabs::close-button:hover { - image: url(:/vectors/window_close_hover.svg); -} -/* ============================================== - 7. FRAME STYLES - ============================================== */ -QWidget#BottomLine { - background-color: #90af13; -} - -QFrame#navbar_header { - background-color: #ffffff; -} - -QFrame#overlayFrame { - background-color: white; - border: 2px solid #e2e8f0; - border-radius: 15px; -} - -QFrame#SvgCard { - border-radius: 12px; - background-color: rgb(255, 255, 255); - border: 2px solid #E5E5E5; -} - -QFrame#SvgCard[state="default"] { - border-radius: 12px; - background-color: rgb(255, 255, 255); - border: 2px solid #E5E5E5; -} - -QFrame#SvgCard[state="selected"] { - background-color: rgb(144, 175, 19); - border: 2px solid #90AF13; - border-radius: 12px; -} - -QFrame#SvgCard[state="hover"] { - background-color: rgb(144, 175, 19); - border: 2px solid #90AF13; - border-radius: 12px; -} - -QFrame#search_result_item { - background: white; - border: 1px solid white; - border-radius: 15px; - padding: 4px 12px; -} - -QFrame#search_result_item:hover { - border: 1px solid #9BC53D; - background: #D5E49B; -} - -/* ============================================== - 8. LABEL STYLES - ============================================== */ -QLabel#version_label { - color: gray; -} - -QLabel#module_section_label { - color: #000000; - font-size: 16px; - background-color: rgba(255, 255, 255, 150); - padding: 2px 0px; - border-top: 1px solid #90AF13; - border-bottom: 1px solid #90AF13; -} - -QLabel#svgCard_title { - font-weight: bold; - background: transparent; - font-size: 13px; - margin-top: 5px; -} - -QLabel#under_dev_label { - color: #000000; - font-size: 16px; -} - -QLabel#svgCard_open_label { - background-color: white; - color: black; - font-weight: bold; - border-top: 2px solid #7ba525; - border-bottom-left-radius: 12px; - border-bottom-right-radius: 12px; - padding: 0px; - margin: 0px; -} - -QLabel#svgCard_open_label[state="default"] { - background-color: white; - color: black; - font-weight: bold; - border-top: 2px solid #7ba525; - border-bottom-left-radius: 12px; - border-bottom-right-radius: 12px; - padding: 0px; - margin: 0px; -} - -QLabel#svgCard_open_label:hover { - background-color: white; - color: #90AF13; - font-weight: bold; - border-top: 2px solid #7ba525; - border-bottom-left-radius: 12px; - border-bottom-right-radius: 12px; - padding: 0px; - margin: 0px; -} - -QLabel#svgCard_open_label[state="selected"] { - background-color: white; - color: black; - font-weight: bold; - border-top: 2px solid #7ba525; - border-bottom-left-radius: 12px; - border-bottom-right-radius: 12px; - padding: 0px; - margin: 0px; -} - -/* Search Overlay Labels */ -QLabel#projectsHeader { - color: #2d3748; - font-size: 12px; - font-weight: bold; - padding: 8px 16px; - border-radius: 12px; - background-color: #f7fafc; - border-bottom: 1px solid #e2e8f0; -} - -QLabel#modulesHeader { - color: #2d3748; - font-size: 12px; - font-weight: bold; - padding: 8px 16px; - border-radius: 12px; - background-color: #f7fafc; - border-top: 1px solid #e2e8f0; - border-bottom: 1px solid #e2e8f0; -} - -QLabel#noResults { - color: #718096; - font-size: 13px; - padding: 24px; -} - -QLabel#primaryText { - color: #1a202c; - font-size: 13px; - font-weight: 600; - background: transparent; -} - -QLabel#secondaryText { - color: #718096; - font-size: 12px; - background: transparent; -} - -QLabel#dateText { - color: #a0aec0; - background: transparent; - font-size: 11px; -} - -QLabel#iconLabel { - color: #2d3748; - font-size: 16px; - background: transparent; - font-weight: bold; -} - -/* ============================================== - 9. SCROLLBAR STYLES - ============================================== */ -QScrollArea#search_overlay_scrollarea QScrollBar:vertical { - border: none; - background: #f7fafc; - width: 3px; - margin-top: 8px; - margin-bottom: 8px; -} - -QScrollArea#search_overlay_scrollarea QScrollBar::handle:vertical { - background: #cbd5e0; - border-radius: 2px; - min-height: 20px; -} - -QScrollArea#search_overlay_scrollarea QScrollBar::handle:vertical:hover { - background: #a0aec0; -} - -QScrollArea#search_overlay_scrollarea QScrollBar::add-line:vertical, -QScrollArea#search_overlay_scrollarea QScrollBar::sub-line:vertical { - border: none; - background: none; -} - -QScrollArea#search_overlay_scrollarea QScrollBar::add-page:vertical, -QScrollArea#search_overlay_scrollarea QScrollBar::sub-page:vertical { - background: none; -} - -QScrollArea#recents_scroll_area{ - background: transparent; - border: none; - padding: 10px; -} - -/* Scrollbar itself */ -QScrollArea#recents_scroll_area QScrollBar:vertical { - border: none; - background: #f0f0f0; - width: 8px; - margin: 0px 0px 0px 0px; -} - -/* Handle */ -QScrollArea#recents_scroll_area QScrollBar::handle:vertical { - background: #c0c0c0; - border-radius: 4px; - min-height: 20px; -} - -/* Handle on hover */ -QScrollArea#recents_scroll_area QScrollBar::handle:vertical:hover { - background: #a0a0a0; -} - -/* Handle when pressed */ -QScrollArea#recents_scroll_area QScrollBar::handle:vertical:pressed { - background: #808080; /* Even darker grey when pressed */ -} - -/* Remove add/sub line buttons (arrows) */ -QScrollArea#recents_scroll_area QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical { - border: none; - background: none; -} - -/* Styling the page area (the track around the handle) */ -QScrollArea#recents_scroll_area QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { - background: none; /* Make the page area transparent, letting the QScrollBar background show through */ -} - -/* Default button style */ -QPushButton#TopButton { - background: #fff; - border-radius: 0px; - border: 1px solid #000000; - color: #000; - font-size: 1px; - padding: 10px; - text-align: center; -} - -/* Hover button style */ -QPushButton#TopButton[hover="true"] { - background: #90AF13; - border: 1px solid #8AB23A; - color: #fff; - font-size: 14px; - font-weight: 600; - padding: 10px; - text-align: center; -} - -/* Pressed button style */ -QPushButton#TopButton[pressed="true"] { - background: #8AB23A; - border: 1px solid #8AB23A; - color: #fff; - font-size: 14px; - font-weight: 600; - padding: 10px; - text-align: center; -} - -/* Dropdown menu style */ -QMenu { - background: #fff; - border: 1px solid #90AF13; - font-size: 14px; - padding: 0px; -} - -QMenu::item { - padding: 8px 16px; - color: #333; - border: none; - margin: 1px; -} - -QMenu::item:selected { - background: #90AF13; - color: #fff; - border-radius: 2px; -} - -QWidget#searchContainer { - border: 2px solid #e1e5e9; - border-radius: 24px; - background: white; - min-height: 48px; - padding: 0px 8px; -} -QWidget#searchContainer[focused="true"] { - border-color: #C8D8A2; -} -QLineEdit#searchInput { - border: none; - background: transparent; - font-size: 16px; - color: #333; -} -QLineEdit#searchInput:focus { - outline: none; - border: none; -} -QLabel#shortcutKey, #lKey { - background: #f5f5f5; - border: 1px solid #ddd; - border-radius: 4px; - color: #666; - font-size: 11px; - font-weight: 600; - padding: 2px 4px; -} -QLabel#plusLabel { - color: #888; - font-size: 12px; - font-weight: 500; -} - -QFrame#projectItem{ - background: #f8f9fa; - border: 1px solid #e2e8f0; - margin: 2px 4px 6px 4px; - border-radius: 10px; -} -QFrame#projectItem:hover{ - background: #D5E49B; - border-color: #9BC53D; -} -QFrame#projectItem[selected="true"] { - background: #D5E49B; - border-color: #9BC53D; -} -QLabel#projectName{ - color: #1a202c; - font-size: 13px; -} -QLabel#submoduleName, -QLabel#dateLabel { - color: #718096; - font-size: 11px; -} - -QLabel#record_icon_label { - font-weight: bold; - color: #1a202c; - font-size: 16px; -} - -QPushButton#recent_proj_btn { - background-color: #ffffff ; - border-radius: 12px; - color: #2d3748 ; - border: 1px solid #cccccc ; - padding: 4px 5px ; - font-size: 10px ; - font-weight: 600 ; -} -QPushButton#recent_proj_btn:hover { - background-color: #f0f4e3 ; - border-color: #9BC53D ; -} -QPushButton#recent_proj_btn:pressed { - background-color: #d6e6be ; -} - -QFrame#moduleItem { - background: #f8f9fa; - border: 1px solid #e2e8f0; - margin: 2px 4px 6px 4px; - border-radius: 10px; -} -QFrame#moduleItem:hover { - background: #D5E49B; - border-color: #9BC53D; -} -QLabel#moduleName { - color: #1a202c; - font-size: 13px; -} -QLabel#subModuleLabel, QLabel#dateLabel { - color: #718096; - font-size: 11px; -} - -QFrame#sectionFrame { - background: #ffffff; - border: 2px solid #e1e5e9; - border-radius: 18px; -} -QLabel#sectionHeading { - font-family: "Calibri", sans-serif; - font-size: 18px; - font-weight: bold; - color: #000000; - background: transparent; - padding: 8px; -} -QWidget#scrollContainer { - background: transparent; - border: none; - margin: 2px 2px 6px 2px; -} -QWidget#scrollContainer_empty { - background: #f8f9fa; - border: 1px solid #e2e8f0; - margin: 2px 2px 6px 2px; - border-radius: 10px; -} -QWidget#SplashScreen_CentralWidget { - border: 2px solid #90af13; - border-radius: 5px; - background-image: url(:/backgrounds/splash_bg.jpg); - background-repeat: no-repeat; - background-position: center; -} -QLabel#splash_loading_label { - background-color: rgba(255,255,255,0.3); -} - -/*=============================================================== - cad view buttons -===============================================================*/ -QPushButton#cad_view_buttons { - background-color: white; - border: 1px solid black; - border-radius: 0; - color: black; - padding: 0; - font-weight: bold; -} -QPushButton#cad_view_buttons:hover { - background-color: #90AF13; - border: 1px solid #90AF13; - color: white; -} -QPushButton#cad_view_buttons:pressed { - background-color: white; - border: 1px solid #90AF13; - color: black; -} -/*===============================================================*/ - -QWidget#output_dock_indicator, -QWidget#input_dock_indicator { - background-color: white; -} - -/*=======================Template-Page===========================*/ - -QWidget#template_page { - background-color: #ffffff; - margin: 0px; - padding: 0px; -} -QWidget#control_btn_widget { - background-color: #F4F4F4; - padding: 0px; -} -QMenuBar#template_page_menu_bar { - background-color: #F4F4F4; - color: #000000; - padding: 0px; -} -QMenuBar#template_page_menu_bar::item { - padding: 5px 10px; - background: transparent; - border-radius: 0px; -} -QMenuBar#template_page_menu_bar::item:selected { - background: #FFFFFF; -} -QMenuBar#template_page_menu_bar::item:pressed { - background: #E8E8E8; -} -QMenuBar#template_page_menu_bar QMenu { - background-color: #FFFFFF; - border: 1px solid #D0D0D0; - border-radius: 4px; - padding: 0px; -} -QMenuBar#template_page_menu_bar QMenu::item { - padding: 5px; - color: #000000; - font-size: 11px; -} -QMenuBar#template_page_menu_bar QMenu::item:selected { - background-color: #E6F0FF; - border-radius: 3px; -} -QMenuBar#template_page_menu_bar QMenu::separator { - height: 1px; - background: #F0F0F0; - margin-left: 2px; - margin-right: 2px; - margin-top: 0px; - margin-bottom: 0px; -} -QMenuBar#template_page_menu_bar QMenu::right-arrow { - width: 8px; - height: 8px; -} -QWidget#toggle_strip { - background-color: #94b816; -} -QPushButton#toggle_strip_button { - background-color: #6c8408; - color: white; - font-size: 12px; - font-weight: bold; - padding: 0px; - border: none; -} -QPushButton#toggle_strip_button:hover { - background-color: #5e7407; -} - -QWidget#cad_custom_selector { - padding: 5px; -} -QWidget#cad_custom_selector QCheckBox { - color: #000000; - margin: 5px; - border-style: solid; - border-width: 1px; - border-color: transparent; -} -QWidget#cad_custom_selector QCheckBox:disabled { - color: #808086; - margin: 5px; - border-style: solid; - border-width: 1px; - border-color: transparent; -} -QWidget#cad_custom_selector QCheckBox:hover { - border-radius: 4px; - border-style: solid; - border-width: 1px; - border-color: transparent; -} - -QWidget#cad_custom_selector QCheckBox::indicator { - width: 12px; - height: 12px; -} - -QWidget#cad_custom_selector QCheckBox::indicator:checked { - border-style: solid; - border-width: 1px; - border-color: qlineargradient( - spread:pad, x1:0.5, y1:1, x2:0.5, y2:0, - stop:0 rgba(120, 120, 120, 255), - stop:1 rgba(180, 180, 180, 255) - ); - background-color: qlineargradient( - spread:pad, x1:0.5, y1:1, x2:0.5, y2:0, - stop:0 rgba(120, 120, 120, 255), - stop:1 rgba(200, 200, 200, 255) - ); -} - -QWidget#cad_custom_selector QCheckBox::indicator:unchecked { - border-style: solid; - border-width: 1px; - border-color: qlineargradient( - spread:pad, x1:0.5, y1:1, x2:0.5, y2:0, - stop:0 rgba(160, 160, 160, 255), - stop:1 rgba(200, 200, 200, 255) - ); - background-color: #ffffff; -} -/*==========floating-navbar=================================*/ -QWidget#floating_btn_container { - background-color: #FFFFFF; - border: 1px solid #90AF13; - border-top-left-radius: 5px; - border-top-right-radius: 5px; - border-bottom-left-radius: 5px; - border-bottom-right-radius: 5px; -} -QPushButton#floating_btn[state="default"] { - background-color: white; - border: 0px; - border-radius: 5px; - margin: 3px; - padding: 1px; -} -QPushButton#floating_btn[state="selected"] { - background-color: #d1f16a; - border-radius: 5px; - border: 0px; - margin: 3px; - padding: 1px; -} -QPushButton#floating_btn[state="active"] { - background-color: #d1f16a; - border-radius: 5px; - border: 0px; - margin: 3px; - padding: 1px; -} -/*=======================Logs-Dock===========================*/ -QWidget#logs_dock QLabel { - background-color: #F2F2F2; - color: #000000; - padding: 3px; - font-weight: bold; - font-size: 12px; -} -QWidget#logs_dock QTextEdit { - background-color: #F8F8F8; - border: 1px solid #D0D0D0; - font-family: 'Courier New', monospace; - font-size: 12px; - padding: 5px; - color: #000000; -} -QWidget#logs_dock QScrollBar:vertical { - background: #E0E0E0; /* Light grey for the scrollbar track */ - width: 8px; - margin: 0px 0px 0px 3px; - border-radius: 2px; -} -QWidget#logs_dock QScrollBar::handle:vertical { - background: #A0A0A0; /* Medium grey for the scrollbar handle */ - min-height: 30px; - border-radius: 2px; -} -QWidget#logs_dock QScrollBar::handle:vertical:hover { - background: #707070; /* Darker grey on hover for the handle */ -} -QWidget#logs_dock QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical { - height: 0px; /* Hides the up/down arrows */ -} -QWidget#logs_dock QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { - background: none; /* Hides the area between handle and arrows */ -} - -/*=======================Input-Dock===========================*/ - -QScrollArea#inputs_vscrollarea, -QScrollArea#outputs_vscrollarea { - border: 1px solid #EFEFEC; - background-color: transparent; - padding: 3px; -} -QScrollArea#inputs_vscrollarea QScrollBar:vertical, -QScrollArea#outputs_vscrollarea QScrollBar:vertical { - background: #E0E0E0; - width: 8px; - margin: 0px 0px 0px 3px; - border-radius: 2px; -} -QScrollArea#inputs_vscrollarea QScrollBar::handle:vertical, -QScrollArea#outputs_vscrollarea QScrollBar::handle:vertical { - background: #A0A0A0; - min-height: 30px; - border-radius: 2px; -} -QScrollArea#inputs_vscrollarea QScrollBar::handle:vertical:hover, -QScrollArea#outputs_vscrollarea QScrollBar::handle:vertical:hover { - background: #707070; -} -QScrollArea#inputs_vscrollarea QScrollBar::add-line:vertical, -QScrollArea#inputs_vscrollarea QScrollBar::sub-line:vertical, -QScrollArea#outputs_vscrollarea QScrollBar::add-line:vertical, -QScrollArea#outputs_vscrollarea QScrollBar::sub-line:vertical { - height: 0px; -} -QScrollArea#inputs_vscrollarea QScrollBar::add-page:vertical, -QScrollArea#inputs_vscrollarea QScrollBar::sub-page:vertical, -QScrollArea#outputs_vscrollarea QScrollBar::add-page:vertical, -QScrollArea#outputs_vscrollarea QScrollBar::sub-page:vertical { - background: none; -} - -#inputs_container{ - background-color: #FFFFFF; -} -#inputs_container QComboBox{ - padding: 2px; - border: 1px solid black; - border-radius: 5px; - background-color: white; - color: black; -} -#inputs_container QComboBox::drop-down{ - subcontrol-origin: padding; - subcontrol-position: top right; - border-left: 0px; -} -#inputs_container QComboBox::down-arrow{ - image: url(:/images/down_arrow_light.png); - width: 15px; - height: 15px; - margin-right: 5px; -} -#inputs_container QComboBox QAbstractItemView{ - background-color: white; - border: 1px solid black; - outline: none; -} -#inputs_container QComboBox QAbstractItemView::item{ - color: black; - background-color: white; - border: none; - border: 1px solid white; - border-radius: 0; - padding: 2px; -} -#inputs_container QComboBox QAbstractItemView::item:hover{ - border: 1px solid #90AF13; - background-color: #90AF13; - color: black; -} -#inputs_container QComboBox QAbstractItemView::item:selected{ - background-color: #90AF13; - color: black; - border: 1px solid #90AF13; -} -#inputs_container QComboBox QAbstractItemView::item:selected:hover{ - background-color: #90AF13; - color: black; - border: 1px solid #94b816; -} -#inputs_container QLineEdit { - padding: 1px 7px; - border: 1px solid #070707; - border-radius: 6px; - background-color: white; - color: #000000; - font-weight: normal; -} -#inputs_container QGroupBox{ - border: 1px solid #90AF13; - border-radius: 4px; - margin-top: 0.8em; - font-weight: bold; -} -#inputs_container QGroupBox::title{ - subcontrol-origin: content; - subcontrol-position: top left; - left: 10px; - padding: 0 4px; - margin-top: -12px; - background-color: white; -} - -QWidget#outputs_group_container, -QWidget#outputs_container{ - background-color: #FFFFFF; -} - -#outputs_container QLineEdit { - padding: 2px 7px; - border: 1px solid #070707; - border-radius: 4px; - background-color: white; - color: #000000; - font-weight: normal; -} -#outputs_container QGroupBox { - border: 1px solid #90AF13; - border-radius: 4px; - margin-top: 0.8em; - font-weight: bold; -} -#outputs_container QGroupBox::title { - subcontrol-origin: content; - subcontrol-position: top left; - left: 10px; - padding: 0 4px; - margin-top: -12px; - background-color: white; -} -#outputs_container QPushButton { - padding: 3px 9px; - background-color: #888; - color: white; - border: 0px; - border-radius: 4px; - min-width: 100px; - max-width: 120px; - font-size: 12px; -} -#outputs_container QPushButton:disabled { - background-color: #cccccc; - color: #888888; -} - -QPushButton#inputs_button, -QPushButton#outputs_button { - background-color: #94b816; - border: 0px; - color: white; - font-weight: bold; - border-radius: 4px; - padding: 7px 14px; -} -QWidget #inputs-leftpanel { - background-color: white; -} - -QWidget #outputs-rightpanel { - background-color: white; -} - -QDialog#custom_material_popup { - background-color: #ffffff; - color: #000000; - border: 1px solid #90AF13; -} -#custom_material_popup QLabel, -QDialog#spacing_capacity_details QLabel, -QDialog#spacing_dialog QLabel { - color: #000000; -} -#custom_material_popup QLineEdit { - background-color: #ffffff; - color: #000000; - border: 1px solid #c0c0c0; - border-radius: 3px; - padding: 2px 4px; -} -#custom_material_popup QLineEdit[readOnly="true"] { - background-color: #f0f0f0; -} -#custom_material_popup QLineEdit:focus { - border: 1px solid #90af13; -} -#custom_material_popup QPushButton { - background-color: #90af13; - color: #ffffff; - padding: 6px 16px; - border-radius: 3px; - border: none; - font-weight: bold; -} -#custom_material_popup QPushButton:hover { - background-color: #7a9a12; -} -#custom_material_popup QPushButton:pressed { - background-color: #5f7a0e; -} - -QScrollArea#inputs_hscrollarea, -QScrollArea#outputs_hscrollarea { - border: none; - background: transparent; -} -QScrollArea#inputs_hscrollarea QScrollBar:horizontal, -QScrollArea#outputs_hscrollarea QScrollBar:horizontal { - background: #E0E0E0; - height: 8px; - margin: 3px 0px 0px 0px; - border-radius: 2px; -} -QScrollArea#inputs_hscrollarea QScrollBar::handle:horizontal, -QScrollArea#outputs_hscrollarea QScrollBar::handle:horizontal { - background: #A0A0A0; - min-width: 30px; - border-radius: 2px; -} -QScrollArea#inputs_hscrollarea QScrollBar::handle:horizontal:hover, -QScrollArea#outputs_hscrollarea QScrollBar::handle:horizontal:hover { - background: #707070; -} -QScrollArea#inputs_hscrollarea QScrollBar::add-line:horizontal, QScrollBar::sub-line:horizontal, -QScrollArea#outputs_hscrollarea QScrollBar::add-line:horizontal, QScrollBar::sub-line:horizontal { - width: 0px; -} -QScrollArea#inputs_hscrollarea QScrollBar::add-page:horizontal, QScrollBar::sub-page:horizontal, -QScrollArea#outputs_hscrollarea QScrollBar::add-page:horizontal, QScrollBar::sub-page:horizontal { - background: none; -} - -/*=======================dialogs===========================*/ -QDialog#spacing_capacity_details QScrollArea{ - background-color: #E0E0E0; - color: #000000; -} -QDialog#spacing_dialog QScrollArea, -QDialog#spacing_dialog QWidget#scroll_widget{ - background-color: #FFFFFF; - color: #000000; -} -QDialog#TutorialsDialog, -QDialog#AskQuestions, -QDialog#AboutOsdagDialog, -QDialog#UpdateDialog, -QDialog#CustomDialog, -QDialog#design_report_popup, -QDialog#customized_input, -QDialog#spacing_dialog{ - background-color: #FFFFFF; - border: 1px solid #90AF13; -} -QDialog#spacing_capacity_details { - background-color: #E0E0E0; - border: 1px solid #90AF13; -} -QWidget#CustomTitleBar { - background-color: #f4f4f4; -} -QWidget#BottomLine { - background-color: #90AF13; -} -QLabel#message_lbl1 { - font-size: 14px; - font-weight: bold; - color: #444444; -} -QLabel#message_lbl2 { - font-size: 12px; - color: #555555; -} -QLabel#TitleLabel { - color: #000000; - padding: 0px; - background: transparent; -} -QLabel#LogoLabel { - background: transparent; - color: #ffffff; - font-size: 14px; -} - -QToolButton#MinimizeButton { - background-color: transparent; - color: #000000; - border: 0px; - border-radius: 0px; - font-size: 16px; - border-radius: 0px; -} - -QToolButton#MinimizeButton:hover { - background-color: #f1f1f1; -} - -QToolButton#MinimizeButton:pressed { - background-color: #a6a6a6; -} - -QToolButton#MaxRestoreButton { - background-color: transparent; - color: #000000; - border: 0px; - border-radius: 0px; - font-size: 16px; - border-radius: 0px; -} - -QToolButton#MaxRestoreButton:hover { - background-color: #f1f1f1; -} - -QToolButton#MaxRestoreButton:pressed { - background-color: #a6a6a6; -} - -QToolButton#CloseButton { - background-color: transparent; - color: #000000; - border: 0px; - border-radius: 0px; - font-size: 16px; - border-radius: 0px; -} - -QToolButton#CloseButton:hover { - background-color: #e74c3c; - color: #ffffff; -} - -QToolButton#CloseButton:pressed { - background-color: #c0392b; -} - -QDialog#AboutOsdagDialog QWidget#ContentWidget, -QDialog#UpdateDialog QWidget#ContentWidget, -QDialog#customized_input QWidget#ContentWidget { - background-color: #ffffff; -} -QDialog#AboutOsdagDialog QTextBrowser, -QDialog#UpdateDialog QTextBrowser { - background-color: #ffffff; - border: 1px solid #e0e0e0; - border-radius: 4px; - font-family: 'Arial', sans-serif; - font-size: 8pt; - padding: 8px; -} -QDialog#AboutOsdagDialog QPushButton, -QDialog#UpdateDialog QPushButton { - background-color: #90AF13; - color: white; - border: none; - border-radius: 5px; - padding: 5px 20px; - font-size: 12px; - font-weight: bold; -} -QDialog#design_report_popup QPushButton, -QDialog#spacing_capacity_details QPushButton { - background-color: white; - color: black; - font-weight: bold; - border-radius: 5px; - border: 1px solid black; - padding: 5px 14px; - text-align: center; -} -QDialog#AboutOsdagDialog QPushButton:hover, -QDialog#UpdateDialog QPushButton:hover { - background-color: #7A9611; -} -QDialog#AboutOsdagDialog QPushButton:pressed, -QDialog#UpdateDialog QPushButton:pressed { - background-color: #6B850F; -} -QDialog#design_report_popup QPushButton { - background-color: #FFFFFF; - color: #000000; - font-weight: bold; - border-radius: 5px; - border: 1px solid #000000; - padding: 5px 14px; - text-align: center; -} -QDialog#design_report_popup QPushButton:hover, -QDialog#spacing_capacity_details QPushButton:hover { - background-color: #90AF13; - border: 1px solid #90AF13; - color: white; -} -QDialog#design_report_popup QPushButton:pressed, -QDialog#spacing_capacity_details QPushButton:pressed { - color: black; - background-color: white; - border: 1px solid black; -} -QDialog#design_report_popup QLineEdit, -QDialog#design_report_popup QTextEdit, -QDialog#spacing_dialog QLineEdit { - padding: 2px 7px; - border: 1px solid #A6A6A6; - border-radius: 4px; - background-color: white; - color: #000000; - font-weight: normal; -} - -QWidget#report_customization_widget QTreeWidget { - border: 1px solid #90AF13; - border-radius: 6px; - background-color: #ffffff; - show-decoration-selected: 1; -} -QWidget#report_customization_widget QTreeWidget::item { - padding: 4px 6px; - background-color: #f9f9f9; - border-bottom: 1px solid #e0e0e0; -} -QWidget#report_customization_widget QTreeWidget::item:selected { - background-color: #d9e9c7; - color: #000000; -} -QWidget#report_customization_widget QTreeWidget::item:hover { - background-color: #eef5e6; -} -QWidget#report_customization_widget QTreeWidget::indicator { - width: 14px; - height: 14px; -} -QWidget#report_customization_widget QTreeWidget::indicator:unchecked { - border: 1px solid #555555; - background: #ffffff; -} -QWidget#report_customization_widget QTreeWidget::indicator:checked { - border: 1px solid #555555; - background: #90AF13; - image: none; -} -QWidget#report_customization_widget QHeaderView::section { - background-color: #e1e1e1; - color: #000000; - padding-left: 5px; - border-radius: 0px; - border-top-left-radius: 6px; - border-top-right-radius: 6px; -} -QWidget#report_customization_widget QTreeWidget QHeaderView { - border-top-left-radius: 6px; - border-top-right-radius: 6px; -} -/*=============customized-inputs======================*/ -QDialog#customized_input QLabel#label, -QDialog#customized_input QLabel#label_2 { - font-weight: bold; - font-size: 13px; - color: #333333; - padding: 2px; -} -QDialog#customized_input QLabel#note_label { - background-color: #f9f9f9; - border: 1px solid #e0e0e0; - border-radius: 5px; - padding: 10px; - color: #555555; - font-size: 12px; -} -QDialog#customized_input QListWidget { - background-color: white; - border: 1px solid #d0d0d0; - border-radius: 5px; - padding: 5px; -} -QDialog#customized_input QListWidget::item { - border-left: 2px solid transparent; - background-color: transparent; - color: #000000; - padding: 3px; - margin: 1px; -} -QDialog#customized_input QListWidget::item:hover { - background-color: #f0f7d0; - border-left-color: #90af13; -} -QDialog#customized_input QListWidget::item:selected { - background-color: #e8f4c8; - color: black; - border-left: 2px solid #90AF13; -} -QDialog#customized_input QListWidget::item:selected:hover { - background-color: #d8e4b8; - border-left-color: #7a9a12; -} -QDialog#customized_input QListWidget::item:disabled { - color: #999999; - background-color: #f5f5f5; -} -QDialog#customized_input QScrollBar:vertical { - background: #f0f0f0; - width: 10px; - margin: 0; - border-radius: 5px; -} -QDialog#customized_input QScrollBar::handle:vertical { - background: #c0c0c0; - min-height: 30px; - border-radius: 5px; -} -QDialog#customized_input QScrollBar::handle:vertical:hover { - background: #90AF13; -} -QDialog#customized_input QPushButton#pushButton, -QDialog#customized_input QPushButton#pushButton_2, -QDialog#customized_input QPushButton#pushButton_3, -QDialog#customized_input QPushButton#pushButton_4, -QDialog#customized_input QPushButton#pushButton_5 { - background-color: #90AF13; - color: white; - font-weight: bold; - font-size: 14px; - border-radius: 5px; - border: none; - padding: 5px; -} -QDialog#customized_input QPushButton#pushButton:hover, -QDialog#customized_input QPushButton#pushButton_2:hover, -QDialog#customized_input QPushButton#pushButton_3:hover, -QDialog#customized_input QPushButton#pushButton_4:hover, -QDialog#customized_input QPushButton#pushButton_5:hover { - background-color: #7a9a12; -} -QDialog#customized_input QPushButton#pushButton:pressed, -QDialog#customized_input QPushButton#pushButton_2:pressed, -QDialog#customized_input QPushButton#pushButton_3:pressed, -QDialog#customized_input QPushButton#pushButton_4:pressed, -QDialog#customized_input QPushButton#pushButton_5:pressed { - background-color: #5f7a0e; -} -QDialog#customized_input QPushButton#pushButton:disabled, -QDialog#customized_input QPushButton#pushButton_2:disabled, -QDialog#customized_input QPushButton#pushButton_3:disabled, -QDialog#customized_input QPushButton#pushButton_4:disabled, -QDialog#customized_input QPushButton#pushButton_5:disabled { - background-color: #d0d0d0; - color: #888888; -} -/*=======================Additional-Inputs=======================================*/ -QDialog#AdditionalInputs{ - background-color: #FFFFFF; - border: 1px solid #90AF13; -} -QWidget#TableWidget QTabWidget QWidget{ - background-color: #ffffff; -} -QWidget#TableWidget QTabWidget > QWidget > QWidget > QWidget{ - background-color: #ffffff; - border: 1px solid #D0D0D0; -} -#AdditionalInputs QTabBar::tab { - background: #F4F4F4; - border: 1px solid #d9d7d7; - padding: 6px 18px 6px 18px; - color: #000000; - margin-left: 0px; -} -#AdditionalInputs QTabBar::tab:selected { - background: #ffffff; - color: #000000; - border: 1px solid #000000; - border-bottom: 1px solid #ffffff; - padding: 6px 18px 6px 18px; -} -#AdditionalInputs QTabBar::tab:hover { - border: 1px solid #000000; -} -/* Additional Input Button */ -QDialog#AdditionalInputs QPushButton { - background-color: white; - color: black; - font-weight: bold; - border-radius: 5px; - border: 1px solid black; - padding: 5px 14px; - text-align: center; -} -QDialog#AdditionalInputs QPushButton:hover { - background-color: #90AF13; - border: 1px solid #90AF13; - color: white; -} -QDialog#AdditionalInputs QPushButton:pressed { - color: black; - background-color: white; - border: 1px solid black; -} -QDialog#AdditionalInputs QLineEdit { - padding: 2px 7px; - border: 1px solid #A6A6A6; - border-radius: 4px; - background-color: white; - color: #000000; - font-weight: normal; -} -QDialog#AdditionalInputs QComboBox{ - padding: 2px 7px; - border: 1px solid #A6A6A6; - border-radius: 4px; - background-color: white; - color: black; -} -QDialog#AdditionalInputs QComboBox::drop-down{ - subcontrol-origin: padding; - subcontrol-position: top right; - border-left: 0px; -} -QDialog#AdditionalInputs QComboBox::down-arrow{ - image: url(:/images/down_arrow_light.png); - width: 15px; - height: 15px; - margin-right: 5px; -} -QDialog#AdditionalInputs QComboBox QAbstractItemView{ - background-color: white; - border: 1px solid black; - outline: none; -} -QDialog#AdditionalInputs QComboBox QAbstractItemView::item{ - color: black; - background-color: white; - border: none; - border: 1px solid white; - border-radius: 0; - padding: 2px; -} -QDialog#AdditionalInputs QComboBox QAbstractItemView::item:hover{ - border: 1px solid #90AF13; - background-color: #90AF13; - color: black; -} -QDialog#AdditionalInputs QComboBox QAbstractItemView::item:selected{ - background-color: #90AF13; - color: black; - border: 1px solid #90AF13; -} -QDialog#AdditionalInputs QComboBox QAbstractItemView::item:selected:hover{ - background-color: #90AF13; - color: black; - border: 1px solid #94b816; -} -/* Plate-Girder Bounds Dialog */ -QDialog#BoundsSelectorDialog { - background-color: #FFFFFF; - border: 1px solid #90AF13; - border-radius: 0px; -} -QDialog#BoundsSelectorDialog QLineEdit { - background-color: #FFFFFF; - border: 1px solid #CCCCCC; - border-radius: 4px; - padding: 5px 8px; - font-size: 12px; - color: #333333; -} -QDialog#BoundsSelectorDialog QLineEdit:hover { - border: 1px solid #999999; -} -QDialog#BoundsSelectorDialog QLineEdit:focus { - border: 1px solid #90AF13; - background-color: #F5FFF9; -} -QDialog#BoundsSelectorDialog QLabel { - color: #000000; -} \ No newline at end of file diff --git a/src/osdagbridge/desktop/legacy_src/src/osbridge/template_page.py b/src/osdagbridge/desktop/legacy_src/src/osbridge/template_page.py deleted file mode 100644 index c4b74502..00000000 --- a/src/osdagbridge/desktop/legacy_src/src/osbridge/template_page.py +++ /dev/null @@ -1,480 +0,0 @@ -import sys -from PySide6.QtWidgets import ( - QApplication, - QWidget, - QVBoxLayout, - QHBoxLayout, - QLabel, - QMenuBar, - QSplitter, - QSizePolicy, - QPushButton, - QCheckBox, - QScrollArea, - QFrame, -) -from PySide6.QtCore import Qt - -#from input_dock import InputDock, NoScrollComboBox, apply_field_style -#from backend import BackendOsBridge -#from common import * -from PySide6.QtCore import Qt, QFile, QTextStream -from PySide6.QtGui import QIcon - -# Import resources to register them -from osbridge.resources import resources_rc - -from osbridge.ui.input_dock import InputDock, NoScrollComboBox, apply_field_style -from osbridge.ui.output_dock import OutputDock -from osbridge.backend.backend import BackendOsBridge -from osbridge.backend.common import * - - -class DummyCADWidget(QWidget): - """Placeholder for CAD widget""" - - def __init__(self): - super().__init__() - layout = QVBoxLayout(self) - label = QLabel("CAD Window\n(Placeholder)") - label.setAlignment(Qt.AlignCenter) - label.setStyleSheet( - """ - QLabel { - background-color: #f0f0f0; - border: 1px solid #999; - padding: 40px; - font-size: 18px; - color: #666; - } - """ - ) - layout.addWidget(label) - - -class OutputDock(QWidget): - """Output dock styled to match the provided mockup.""" - - def __init__(self): - super().__init__() - self.setObjectName("outputDock") - self.setStyleSheet( - """ - QWidget#outputDock { - background-color: #fdfdf8; - border-left: 2px solid #d7d9c8; - } - """ - ) - self.init_ui() - - def init_ui(self): - main_layout = QVBoxLayout(self) - main_layout.setContentsMargins(8, 8, 8, 8) - main_layout.setSpacing(12) - - title_bar = QWidget() - title_bar.setFixedHeight(40) - title_bar.setObjectName("outputHeader") - title_bar.setStyleSheet( - """ - QWidget#outputHeader { - background-color: #90AF13; - border-radius: 12px; - } - """ - ) - title_layout = QHBoxLayout(title_bar) - title_layout.setContentsMargins(14, 0, 14, 0) - - title_label = QLabel("Output Dock") - title_label.setStyleSheet("color: white; font-size: 13px; font-weight: bold;") - title_layout.addWidget(title_label) - title_layout.addStretch() - main_layout.addWidget(title_bar) - - scroll = QScrollArea() - scroll.setWidgetResizable(True) - scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) - scroll.setStyleSheet("QScrollArea { border: none; background: transparent; }") - - scroll_content = QWidget() - scroll_layout = QVBoxLayout(scroll_content) - scroll_layout.setContentsMargins(0, 0, 0, 0) - scroll_layout.setSpacing(14) - - analysis_frame, analysis_body_layout = self._create_section_frame("Analysis Results") - self._populate_analysis_section(analysis_body_layout) - scroll_layout.addWidget(analysis_frame) - - design_frame, design_body_layout = self._create_section_frame("Design") - self._populate_design_section(design_body_layout) - scroll_layout.addWidget(design_frame) - - scroll_layout.addStretch() - scroll.setWidget(scroll_content) - main_layout.addWidget(scroll) - - button_style = """ - QPushButton { - background-color: #90AF13; - color: white; - font-weight: bold; - border: none; - border-radius: 14px; - padding: 10px 16px; - font-size: 11px; - } - QPushButton:hover { - background-color: #7b980f; - } - """ - - results_btn = QPushButton("Generate Results Table") - results_btn.setStyleSheet(button_style) - main_layout.addWidget(results_btn) - - report_btn = QPushButton("Generate Report") - report_btn.setStyleSheet(button_style) - main_layout.addWidget(report_btn) - - def _create_section_frame(self, title: str): - frame = QFrame() - frame.setObjectName("outputSection") - frame.setStyleSheet( - """ - QFrame#outputSection { - border: 1px solid #cdd874; - border-radius: 18px; - background-color: white; - } - """ - ) - - outer_layout = QVBoxLayout(frame) - outer_layout.setContentsMargins(16, 12, 16, 16) - outer_layout.setSpacing(10) - - header_layout = QHBoxLayout() - header_layout.setContentsMargins(0, 0, 0, 0) - - title_label = QLabel(title) - title_label.setStyleSheet("font-size: 11px; font-weight: bold; color: #2d2d2d;") - header_layout.addWidget(title_label) - header_layout.addStretch() - - toggle_btn = QPushButton("-") - toggle_btn.setObjectName("sectionToggle") - toggle_btn.setCheckable(True) - toggle_btn.setChecked(True) - toggle_btn.setFixedSize(22, 22) - toggle_btn.setStyleSheet( - """ - QPushButton#sectionToggle { - background-color: white; - border: 1px solid #93ad1d; - border-radius: 11px; - color: #6d7a13; - font-weight: bold; - } - QPushButton#sectionToggle:hover { - background-color: #f5f5f5; - } - """ - ) - header_layout.addWidget(toggle_btn) - outer_layout.addLayout(header_layout) - - accent_line = QFrame() - accent_line.setFixedHeight(2) - accent_line.setStyleSheet("background-color: #90AF13; border: none;") - outer_layout.addWidget(accent_line) - - body_widget = QWidget() - body_layout = QVBoxLayout(body_widget) - body_layout.setContentsMargins(0, 0, 0, 0) - body_layout.setSpacing(10) - outer_layout.addWidget(body_widget) - - def on_toggle(checked): - body_widget.setVisible(checked) - toggle_btn.setText("-" if checked else "+") - - toggle_btn.toggled.connect(on_toggle) - - return frame, body_layout - - def _populate_analysis_section(self, layout: QVBoxLayout): - member_row = QHBoxLayout() - member_row.setSpacing(10) - member_label = QLabel("Member:") - member_label.setStyleSheet("font-size: 10px; color: #333;") - member_label.setMinimumWidth(90) - self.member_combo = NoScrollComboBox() - self.member_combo.addItems(["All"]) - apply_field_style(self.member_combo) - member_row.addWidget(member_label) - member_row.addWidget(self.member_combo) - layout.addLayout(member_row) - - load_row = QHBoxLayout() - load_row.setSpacing(10) - load_label = QLabel("Load Combination:") - load_label.setStyleSheet("font-size: 10px; color: #333;") - load_label.setMinimumWidth(90) - self.load_combo = NoScrollComboBox() - self.load_combo.addItems(["Envelope"]) - apply_field_style(self.load_combo) - load_row.addWidget(load_label) - load_row.addWidget(self.load_combo) - layout.addLayout(load_row) - - forces_grid = QHBoxLayout() - forces_grid.setSpacing(12) - for items in (("Fx", "Mx", "Dx"), ("Fy", "My", "Dy"), ("Fz", "Mz", "Dz")): - column = QVBoxLayout() - column.setSpacing(6) - for text in items: - cb = QCheckBox(text) - cb.setStyleSheet("font-size: 10px; color: #333;") - column.addWidget(cb) - forces_grid.addLayout(column) - layout.addLayout(forces_grid) - - display_label = QLabel("Display Options:") - display_label.setStyleSheet("font-size: 10px; color: #333;") - layout.addWidget(display_label) - - display_row = QHBoxLayout() - display_row.setSpacing(12) - for text in ("Max", "Min"): - cb = QCheckBox(text) - cb.setStyleSheet("font-size: 10px; color: #333;") - display_row.addWidget(cb) - display_row.addStretch() - layout.addLayout(display_row) - - utilization_check = QCheckBox("Controlling Utilization Ratio") - utilization_check.setStyleSheet("font-size: 10px; color: #333;") - layout.addWidget(utilization_check) - - def _populate_design_section(self, layout: QVBoxLayout): - super_frame = self._create_design_subframe("Superstructure", ["Steel Design", "Deck Design"]) - layout.addWidget(super_frame) - - sub_frame = self._create_design_subframe("Substructure") - layout.addWidget(sub_frame) - - def _create_design_subframe(self, title: str, button_labels=None): - frame = QFrame() - frame.setObjectName("designSubSection") - frame.setStyleSheet( - """ - QFrame#designSubSection { - border: 1px solid #e0e8a4; - border-radius: 14px; - background-color: #fcfdf4; - } - """ - ) - - outer_layout = QVBoxLayout(frame) - outer_layout.setContentsMargins(12, 8, 12, 12) - outer_layout.setSpacing(8) - - header_layout = QHBoxLayout() - header_layout.setContentsMargins(0, 0, 0, 0) - - title_label = QLabel(title) - title_label.setStyleSheet("font-size: 10px; font-weight: bold; color: #333;") - header_layout.addWidget(title_label) - header_layout.addStretch() - - toggle_btn = QPushButton("-") - toggle_btn.setObjectName("designToggle") - toggle_btn.setCheckable(True) - toggle_btn.setChecked(True) - toggle_btn.setFixedSize(22, 22) - toggle_btn.setStyleSheet( - """ - QPushButton#designToggle { - background-color: white; - border: 1px solid #bcc66d; - border-radius: 11px; - color: #6d7a13; - font-weight: bold; - } - QPushButton#designToggle:hover { - background-color: #f5f5f5; - } - """ - ) - header_layout.addWidget(toggle_btn) - outer_layout.addLayout(header_layout) - - body_widget = QWidget() - body_layout = QVBoxLayout(body_widget) - body_layout.setContentsMargins(0, 0, 0, 0) - body_layout.setSpacing(6) - outer_layout.addWidget(body_widget) - - if button_labels: - for text in button_labels: - btn = QPushButton(text) - btn.setObjectName("designActionBtn") - btn.setStyleSheet( - """ - QPushButton#designActionBtn { - background-color: white; - color: #3b3b3b; - border: 1px solid #c7c7c7; - border-radius: 14px; - padding: 8px; - font-size: 10px; - font-weight: bold; - } - QPushButton#designActionBtn:hover { - background-color: #f7f7f7; - } - """ - ) - body_layout.addWidget(btn) - else: - placeholder = QLabel(" ") - placeholder.setMinimumHeight(28) - body_layout.addWidget(placeholder) - - def on_toggle(checked): - body_widget.setVisible(checked) - toggle_btn.setText("-" if checked else "+") - - toggle_btn.toggled.connect(on_toggle) - - return frame - - -class DummyLogDock(QWidget): - """Placeholder for log dock.""" - - def __init__(self): - super().__init__() - layout = QVBoxLayout(self) - label = QLabel("Log Window\n(Placeholder)") - label.setAlignment(Qt.AlignCenter) - label.setStyleSheet( - """ - QLabel { - background-color: #fff3e0; - border: 2px dashed #ff9800; - padding: 20px; - font-size: 14px; - color: #e65100; - } - """ - ) - layout.addWidget(label) - self.hide() - - -class CustomWindow(QWidget): - def __init__(self, title: str, backend: object, parent=None): - super().__init__() - self.parent = parent - self.backend = backend() - - self.setWindowTitle(title) - self.setStyleSheet( - """ - QWidget { - background-color: #ffffff; - margin: 0px; - padding: 0px; - } - QMenuBar { - background-color: #F4F4F4; - color: #000000; - padding: 0px; - } - QMenuBar::item { - padding: 5px 10px; - background: transparent; - } - QMenuBar::item:selected { - background: #FFFFFF; - } - """ - ) - - self.init_ui() - - def init_ui(self): - main_v_layout = QVBoxLayout(self) - main_v_layout.setContentsMargins(0, 0, 0, 0) - main_v_layout.setSpacing(0) - - self.menu_bar = QMenuBar(self) - self.menu_bar.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed) - self.menu_bar.setFixedHeight(28) - self.menu_bar.setContentsMargins(0, 0, 0, 0) - self.menu_bar.addMenu("File") - self.menu_bar.addMenu("Edit") - self.menu_bar.addMenu("Graphics") - self.menu_bar.addMenu("Help") - main_v_layout.addWidget(self.menu_bar) - - body_widget = QWidget() - body_layout = QHBoxLayout(body_widget) - body_layout.setContentsMargins(0, 0, 0, 0) - body_layout.setSpacing(0) - - # Main horizontal splitter - main_splitter = QSplitter(Qt.Horizontal, body_widget) - - # Input dock - input_dock = InputDock(backend=self.backend, parent=self) - input_dock.setMinimumWidth(380) - input_dock.setMaximumWidth(450) - main_splitter.addWidget(input_dock) - - # CAD widget - cad_widget = DummyCADWidget() - main_splitter.addWidget(cad_widget) - - # Output dock - output_dock = OutputDock() - output_dock.setMinimumWidth(350) - output_dock.setMaximumWidth(450) - main_splitter.addWidget(output_dock) - - body_layout.addWidget(main_splitter) - - # Set stretch factors for main splitter - main_splitter.setStretchFactor(0, 0) # Input dock - fixed - main_splitter.setStretchFactor(1, 1) # Central area - expandable - main_splitter.setStretchFactor(2, 0) # Output dock - fixed - - # Set initial sizes - input_dock_width = 350 - output_dock_width = 280 - total_width = self.width() if self.width() > 0 else 1200 - central_width = max(500, total_width - input_dock_width - output_dock_width) - main_splitter.setSizes([input_dock_width, central_width, output_dock_width]) - - main_v_layout.addWidget(body_widget) - - # Store references - self.main_splitter = main_splitter - self.input_dock = input_dock - self.output_dock = output_dock - self.cad_widget = cad_widget - - -def main(): - app = QApplication(sys.argv) - window = CustomWindow("Osdag Bridge", BackendOsBridge) - window.showMaximized() - window.show() - sys.exit(app.exec()) - -if __name__ == "__main__": - main() \ No newline at end of file diff --git a/src/osdagbridge/desktop/legacy_src/src/osbridge/ui/__init__.py b/src/osdagbridge/desktop/legacy_src/src/osbridge/ui/__init__.py deleted file mode 100644 index a1459dfc..00000000 --- a/src/osdagbridge/desktop/legacy_src/src/osbridge/ui/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -# UI package - diff --git a/src/osdagbridge/desktop/legacy_src/src/osbridge/ui/additional_inputs.py b/src/osdagbridge/desktop/legacy_src/src/osbridge/ui/additional_inputs.py deleted file mode 100644 index 1203a534..00000000 --- a/src/osdagbridge/desktop/legacy_src/src/osbridge/ui/additional_inputs.py +++ /dev/null @@ -1,1996 +0,0 @@ -""" -Additional Inputs Widget for Highway Bridge Design -Provides detailed input fields for manual bridge parameter definition -""" -import sys -import os -from PySide6.QtWidgets import ( - QWidget, QVBoxLayout, QHBoxLayout, QTabWidget, QLabel, QLineEdit, - QComboBox, QGroupBox, QFormLayout, QPushButton, QScrollArea, - QCheckBox, QMessageBox, QSizePolicy, QSpacerItem, QStackedWidget, - QFrame, QGridLayout -) -from PySide6.QtCore import Qt, Signal -from PySide6.QtGui import QDoubleValidator, QIntValidator - -from osbridge.backend.common import * - - -def get_combobox_style(): - """Return the common stylesheet for dropdowns with the SVG icon from resources.""" - return """ - QComboBox { - padding: 6px 42px 6px 14px; - border: 1px solid #b8b8b8; - border-radius: 8px; - background-color: #ffffff; - color: #2b2b2b; - font-size: 12px; - min-height: 34px; - } - QComboBox:hover { - border: 1px solid #909090; - } - QComboBox:focus { - border: 1px solid #4a7ba7; - } - QComboBox::drop-down { - subcontrol-origin: padding; - subcontrol-position: center right; - width: 26px; - height: 26px; - width: 26px; - height: 26px; - border: none; - background: transparent; - right: 8px; - } - QComboBox::down-arrow { - image: url(:/vectors/arrow_down_light.svg); - width: 16px; - height: 16px; - } - /* when popup is open show the up arrow */ - QComboBox::down-arrow:on { - image: url(:/vectors/arrow_up_light.svg); - width: 16px; - height: 16px; - } - QComboBox QAbstractItemView {{ - border: 1px solid #b8b8b8; - background: #ffffff; - selection-background-color: #e7f2ff; - selection-color: #1f1f1f; - }} - QComboBox QAbstractItemView::item {{ - padding: 6px 10px; - font-size: 12px; - }} - """ - - -def get_lineedit_style(): - """Return the shared stylesheet for line edits in the section inputs.""" - return """ - QLineEdit { - padding: 6px 12px; - border: 1px solid #b8b8b8; - border-radius: 8px; - background-color: #ffffff; - color: #2b2b2b; - font-size: 12px; - min-height: 34px; - } - QLineEdit:hover { - border: 1px solid #909090; - } - QLineEdit:focus { - border: 1px solid #4a7ba7; - } - QLineEdit:disabled { - background-color: #f0f0f0; - color: #9b9b9b; - } - """ - - -def apply_field_style(widget): - """Apply the appropriate style to combo boxes and line edits.""" - widget.setMinimumHeight(34) - if isinstance(widget, QComboBox): - widget.setStyleSheet(get_combobox_style()) - elif isinstance(widget, QLineEdit): - widget.setStyleSheet(get_lineedit_style()) - - -SECTION_NAV_BUTTON_STYLE = """ - QPushButton { - background-color: #f4f4f4; - border: 2px solid #d2d2d2; - border-radius: 12px; - padding: 20px 16px; - text-align: left; - font-weight: bold; - font-size: 12px; - color: #333333; - } - QPushButton:hover { - border-color: #b5b5b5; - } - QPushButton:checked { - background-color: #90af13; - border-color: #7da523; - color: #ffffff; - } -""" - - -class OptimizableField(QWidget): - """Widget that allows selection between Optimized/Customized/All modes with input field""" - - def __init__(self, label_text, parent=None): - super().__init__(parent) - self.layout = QHBoxLayout(self) - self.layout.setContentsMargins(0, 0, 0, 0) - self.layout.setSpacing(8) - - # Mode selector - self.mode_combo = QComboBox() - self.mode_combo.addItems(VALUES_OPTIMIZATION_MODE) - self.mode_combo.setMinimumWidth(140) - self.mode_combo.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) - - # Input field - self.input_field = QLineEdit() - self.input_field.setEnabled(False) # Disabled by default for "Optimized" - self.input_field.setVisible(False) - self.input_field.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) - - self.layout.addWidget(self.mode_combo) - self.layout.addWidget(self.input_field) - - # Connect signal - self.mode_combo.currentTextChanged.connect(self.on_mode_changed) - self.on_mode_changed(self.mode_combo.currentText()) - - def on_mode_changed(self, text): - """Enable/disable input field based on selection""" - if text in ("Optimized", "All"): - self.input_field.setEnabled(False) - self.input_field.clear() - self.input_field.setVisible(False) - else: - self.input_field.setEnabled(True) - self.input_field.setVisible(True) - - def get_value(self): - """Returns tuple of (mode, value)""" - return (self.mode_combo.currentText(), self.input_field.text()) - - -class BridgeGeometryTab(QWidget): - """Sub-tab for Bridge Geometry inputs""" - - footpath_changed = Signal(str) # Signal when footpath status changes - - def __init__(self, footpath_value="None", carriageway_width=7.5, parent=None): - super().__init__(parent) - self.footpath_value = footpath_value - self.carriageway_width = carriageway_width - self.updating_fields = False # Flag to prevent circular updates - self.init_ui() - - def style_input_field(self, field): - """Apply consistent styling to input fields""" - apply_field_style(field) - - def style_group_box(self, group_box): - """Apply consistent styling to group boxes""" - group_box.setStyleSheet(""" - QGroupBox { - font-weight: bold; - border: 2px solid #d0d0d0; - border-radius: 6px; - margin-top: 12px; - padding-top: 15px; - background-color: #f9f9f9; - } - QGroupBox::title { - subcontrol-origin: margin; - subcontrol-position: top left; - left: 10px; - padding: 0 5px; - background-color: white; - color: #4a7ba7; - } - """) - - def init_ui(self): - main_layout = QVBoxLayout(self) - main_layout.setContentsMargins(0, 0, 0, 0) - main_layout.setSpacing(0) - - # TOP: Diagram placeholder - diagram_widget = QWidget() - diagram_widget.setStyleSheet(""" - QWidget { - background-color: #d9d9d9; - border-bottom: 1px solid #b0b0b0; - } - """) - diagram_widget.setMinimumHeight(150) - diagram_widget.setMaximumHeight(200) - diagram_layout = QVBoxLayout(diagram_widget) - diagram_layout.setContentsMargins(20, 20, 20, 20) - diagram_layout.setAlignment(Qt.AlignCenter) - - # Diagram image placeholder - diagram_label = QLabel("Bridge Geometry\nDiagram") - diagram_label.setAlignment(Qt.AlignCenter) - diagram_label.setStyleSheet(""" - QLabel { - background-color: transparent; - border: none; - padding: 20px; - font-size: 13px; - color: #333; - } - """) - diagram_layout.addWidget(diagram_label, 0, Qt.AlignCenter) - - main_layout.addWidget(diagram_widget) - - # BOTTOM: Tabbed Input Interface - input_container = QWidget() - input_container.setStyleSheet(""" - QWidget { - background-color: white; - } - """) - input_layout = QVBoxLayout(input_container) - input_layout.setContentsMargins(10, 10, 10, 10) - input_layout.setSpacing(0) - - # Create sub-tabs for different input categories - self.input_tabs = QTabWidget() - self.input_tabs.setStyleSheet(""" - QTabWidget::pane { - border: 1px solid #b0b0b0; - border-top: none; - background-color: white; - border-radius: 0px 0px 8px 8px; - } - QTabBar::tab { - background-color: #e8e8e8; - color: #555; - padding: 8px 20px; - border: 1px solid #b0b0b0; - border-bottom: none; - border-right: none; - font-size: 11px; - min-width: 80px; - } - QTabBar::tab:last { - border-right: 1px solid #b0b0b0; - } - QTabBar::tab:selected { - background-color: #90AF13; - color: white; - font-weight: bold; - border: 1px solid #90AF13; - border-bottom: none; - } - QTabBar::tab:hover:!selected { - background-color: #d0d0d0; - } - """) - - # Create each sub-tab - self.create_layout_tab() - self.create_deck_tab() - self.create_crash_barrier_tab() - self.create_railing_tab() - self.create_wearing_course_tab() - self.create_lane_details_tab() - - input_layout.addWidget(self.input_tabs) - - main_layout.addWidget(input_container, 1) - - # Connect deck thickness to footpath thickness - self.deck_thickness.textChanged.connect(self.update_footpath_thickness) - - # Initialize calculations with default values - self.recalculate_girders() - - def create_layout_tab(self): - """Create the Layout tab with girder spacing and deck overhang""" - layout_widget = QWidget() - layout_widget.setStyleSheet("background-color: white;") - layout_layout = QVBoxLayout(layout_widget) - layout_layout.setContentsMargins(25, 25, 25, 25) - layout_layout.setSpacing(20) - - # --- Inputs Group --- - inputs_group = QGroupBox() - inputs_group.setStyleSheet(""" - QGroupBox { - background-color: white; - border: 2px solid #a0a0a0; - border-radius: 10px; - margin-top: 10px; - } - """) - inputs_layout = QVBoxLayout(inputs_group) - inputs_layout.setContentsMargins(20, 20, 20, 20) - inputs_layout.setSpacing(15) - - # Title inside the group - title_label = QLabel("Inputs:") - title_label.setStyleSheet("font-size: 12px; font-weight: bold; color: #000; border: none;") - inputs_layout.addWidget(title_label) - - # Create grid for inputs - grid = QGridLayout() - grid.setHorizontalSpacing(40) - grid.setVerticalSpacing(20) - grid.setColumnStretch(1, 1) - grid.setColumnStretch(3, 1) - - # Row 0: Girder Spacing and No. of Girders - girder_spacing_label = QLabel("Girder Spacing (m):") - girder_spacing_label.setStyleSheet("font-size: 11px; color: #555; border: none;") - girder_spacing_label.setMinimumWidth(150) - self.girder_spacing = QLineEdit() - self.girder_spacing.setValidator(QDoubleValidator(0.01, 50.0, 3)) - self.girder_spacing.setText(str(DEFAULT_GIRDER_SPACING)) - self.style_input_field(self.girder_spacing) - self.girder_spacing.textChanged.connect(self.on_girder_spacing_changed) - - no_girders_label = QLabel("No. of Girders:") - no_girders_label.setStyleSheet("font-size: 11px; color: #555; border: none;") - no_girders_label.setMinimumWidth(150) - self.no_of_girders = QLineEdit() - self.no_of_girders.setValidator(QIntValidator(2, 100)) - self.style_input_field(self.no_of_girders) - self.no_of_girders.textChanged.connect(self.on_no_of_girders_changed) - - grid.addWidget(girder_spacing_label, 0, 0, Qt.AlignLeft) - grid.addWidget(self.girder_spacing, 0, 1) - grid.addWidget(no_girders_label, 0, 2, Qt.AlignLeft) - grid.addWidget(self.no_of_girders, 0, 3) - - # Row 1: Deck Overhang Width - deck_overhang_label = QLabel("Deck Overhang Width (m):") - deck_overhang_label.setStyleSheet("font-size: 11px; color: #555; border: none;") - deck_overhang_label.setMinimumWidth(150) - self.deck_overhang = QLineEdit() - self.deck_overhang.setValidator(QDoubleValidator(0.0, 10.0, 3)) - self.deck_overhang.setText(str(DEFAULT_DECK_OVERHANG)) - self.style_input_field(self.deck_overhang) - self.deck_overhang.textChanged.connect(self.on_deck_overhang_changed) - - grid.addWidget(deck_overhang_label, 1, 0, Qt.AlignLeft) - grid.addWidget(self.deck_overhang, 1, 1) - - inputs_layout.addLayout(grid) - layout_layout.addWidget(inputs_group) - - # --- Overall Bridge Width Group --- - width_group = QGroupBox() - width_group.setStyleSheet(""" - QGroupBox { - background-color: white; - border: 2px solid #a0a0a0; - border-radius: 10px; - margin-top: 10px; - } - """) - width_layout = QHBoxLayout(width_group) - width_layout.setContentsMargins(20, 20, 20, 20) - width_layout.setSpacing(40) - - overall_width_label = QLabel("Overall Bridge Width (m):") - overall_width_label.setStyleSheet("font-size: 11px; color: #555; border: none;") - overall_width_label.setMinimumWidth(150) - - self.overall_width_display = QLineEdit() - self.overall_width_display.setReadOnly(True) - self.style_input_field(self.overall_width_display) - - width_layout.addWidget(overall_width_label) - width_layout.addWidget(self.overall_width_display) - width_layout.addStretch() - - layout_layout.addWidget(width_group) - layout_layout.addStretch() - - self.input_tabs.addTab(layout_widget, "Layout") - - def create_deck_tab(self): - """Create the Deck tab with deck and footpath parameters""" - deck_widget = QWidget() - deck_widget.setStyleSheet("background-color: white;") - deck_layout = QVBoxLayout(deck_widget) - deck_layout.setContentsMargins(25, 25, 25, 25) - deck_layout.setSpacing(20) - - # --- Deck Inputs Group --- - inputs_group = QGroupBox() - inputs_group.setStyleSheet(""" - QGroupBox { - background-color: white; - border: 2px solid #a0a0a0; - border-radius: 10px; - margin-top: 10px; - } - """) - inputs_layout = QVBoxLayout(inputs_group) - inputs_layout.setContentsMargins(20, 20, 20, 20) - inputs_layout.setSpacing(15) - - # Title - title_label = QLabel("Deck Inputs:") - title_label.setStyleSheet("font-size: 12px; font-weight: bold; color: #000; border: none;") - inputs_layout.addWidget(title_label) - - # Create grid for inputs - grid = QGridLayout() - grid.setHorizontalSpacing(40) - grid.setVerticalSpacing(20) - grid.setColumnStretch(1, 1) - grid.setColumnStretch(3, 1) - - # Row 0: Deck Thickness and Decking Plate - deck_thickness_label = QLabel("Deck Thickness:") - deck_thickness_label.setStyleSheet("font-size: 11px; color: #555; border: none;") - deck_thickness_label.setMinimumWidth(150) - self.deck_thickness = QLineEdit() - self.deck_thickness.setValidator(QDoubleValidator(0.0, 500.0, 0)) - self.style_input_field(self.deck_thickness) - - decking_plate_label = QLabel("Decking Plate:") - decking_plate_label.setStyleSheet("font-size: 11px; color: #555; border: none;") - decking_plate_label.setMinimumWidth(150) - self.decking_plate = QComboBox() - self.decking_plate.addItems(VALUES_DECKING_PLATE) - self.style_input_field(self.decking_plate) - - grid.addWidget(deck_thickness_label, 0, 0, Qt.AlignLeft) - grid.addWidget(self.deck_thickness, 0, 1) - grid.addWidget(decking_plate_label, 0, 2, Qt.AlignLeft) - grid.addWidget(self.decking_plate, 0, 3) - - # Row 1: Footpath Width and Footpath Thickness - footpath_width_label = QLabel("Footpath Width (m):") - footpath_width_label.setStyleSheet("font-size: 11px; color: #555; border: none;") - footpath_width_label.setMinimumWidth(150) - self.footpath_width = QLineEdit() - self.footpath_width.setValidator(QDoubleValidator(MIN_FOOTPATH_WIDTH, 5.0, 3)) - self.style_input_field(self.footpath_width) - self.footpath_width.textChanged.connect(self.on_footpath_width_changed) - - footpath_thickness_label = QLabel("Footpath Thickness :") - footpath_thickness_label.setStyleSheet("font-size: 11px; color: #555; border: none;") - footpath_thickness_label.setMinimumWidth(150) - self.footpath_thickness = QLineEdit() - self.footpath_thickness.setValidator(QDoubleValidator(0.0, 500.0, 0)) - self.style_input_field(self.footpath_thickness) - - grid.addWidget(footpath_width_label, 1, 0, Qt.AlignLeft) - grid.addWidget(self.footpath_width, 1, 1) - grid.addWidget(footpath_thickness_label, 1, 2, Qt.AlignLeft) - grid.addWidget(self.footpath_thickness, 1, 3) - - # Row 2: Safety Kerb Thickness and Safety Kerb Width - safety_kerb_thickness_label = QLabel("Safety Kerb Thickness (mm):") - safety_kerb_thickness_label.setStyleSheet("font-size: 11px; color: #555; border: none;") - safety_kerb_thickness_label.setMinimumWidth(150) - self.safety_kerb_thickness = QLineEdit() - self.safety_kerb_thickness.setValidator(QDoubleValidator(0.0, 500.0, 0)) - self.style_input_field(self.safety_kerb_thickness) - - safety_kerb_width_label = QLabel("Safety Kerb Width (m):") - safety_kerb_width_label.setStyleSheet("font-size: 11px; color: #555; border: none;") - safety_kerb_width_label.setMinimumWidth(150) - self.safety_kerb_width = QLineEdit() - self.safety_kerb_width.setValidator(QDoubleValidator(MIN_SAFETY_KERB_WIDTH, 2.0, 3)) - self.style_input_field(self.safety_kerb_width) - - grid.addWidget(safety_kerb_thickness_label, 2, 0, Qt.AlignLeft) - grid.addWidget(self.safety_kerb_thickness, 2, 1) - grid.addWidget(safety_kerb_width_label, 2, 2, Qt.AlignLeft) - grid.addWidget(self.safety_kerb_width, 2, 3) - - # Row 3: Load Case - load_case_label = QLabel("Load Case:") - load_case_label.setStyleSheet("font-size: 11px; color: #555; border: none;") - load_case_label.setMinimumWidth(150) - self.deck_load_case = QComboBox() - self.deck_load_case.addItems(VALUES_LOAD_CASE) - self.style_input_field(self.deck_load_case) - - grid.addWidget(load_case_label, 3, 0, Qt.AlignLeft) - grid.addWidget(self.deck_load_case, 3, 1) - - inputs_layout.addLayout(grid) - deck_layout.addWidget(inputs_group) - deck_layout.addStretch() - - self.input_tabs.addTab(deck_widget, "Deck") - - def create_crash_barrier_tab(self): - """Create the Crash Barrier tab""" - crash_widget = QWidget() - crash_widget.setStyleSheet("background-color: white;") - crash_layout = QVBoxLayout(crash_widget) - crash_layout.setContentsMargins(25, 25, 25, 25) - crash_layout.setSpacing(20) - - # --- Inputs Group --- - inputs_group = QGroupBox() - inputs_group.setStyleSheet(""" - QGroupBox { - background-color: white; - border: 2px solid #a0a0a0; - border-radius: 10px; - margin-top: 10px; - } - """) - inputs_layout = QVBoxLayout(inputs_group) - inputs_layout.setContentsMargins(20, 20, 20, 20) - inputs_layout.setSpacing(15) - - # Title - title_label = QLabel("Inputs:") - title_label.setStyleSheet("font-size: 12px; font-weight: bold; color: #000; border: none;") - inputs_layout.addWidget(title_label) - - # Create grid for inputs - grid = QGridLayout() - grid.setHorizontalSpacing(40) - grid.setVerticalSpacing(20) - grid.setColumnStretch(1, 1) - grid.setColumnStretch(3, 1) - - # Row 0: Crash Barrier Type - crash_type_label = QLabel("Crash Barrier Type:") - crash_type_label.setStyleSheet("font-size: 11px; color: #555; border: none;") - crash_type_label.setMinimumWidth(180) - self.crash_barrier_type = QComboBox() - self.crash_barrier_type.addItems(VALUES_CRASH_BARRIER_TYPE) - self.style_input_field(self.crash_barrier_type) - self.crash_barrier_type.currentTextChanged.connect(self.on_crash_barrier_type_changed) - - grid.addWidget(crash_type_label, 0, 0, Qt.AlignLeft) - grid.addWidget(self.crash_barrier_type, 0, 1, 1, 3) - - # Row 1: Crash Barrier Width - crash_width_label = QLabel("Crash Barrier Width (m):") - crash_width_label.setStyleSheet("font-size: 11px; color: #555; border: none;") - crash_width_label.setMinimumWidth(180) - self.crash_barrier_width = QLineEdit() - self.crash_barrier_width.setValidator(QDoubleValidator(0.0, 2.0, 3)) - self.crash_barrier_width.setText(str(DEFAULT_CRASH_BARRIER_WIDTH)) - self.style_input_field(self.crash_barrier_width) - self.crash_barrier_width.textChanged.connect(self.recalculate_girders) - - grid.addWidget(crash_width_label, 1, 0, Qt.AlignLeft) - grid.addWidget(self.crash_barrier_width, 1, 1, 1, 3) - - # Row 2: Crash Barrier Material density - crash_density_label = QLabel("Crash Barrier Material density") - crash_density_label.setStyleSheet("font-size: 11px; color: #555; border: none;") - crash_density_label.setMinimumWidth(180) - self.crash_barrier_density = QLineEdit() - self.crash_barrier_density.setValidator(QDoubleValidator(0.0, 100.0, 2)) - self.style_input_field(self.crash_barrier_density) - - grid.addWidget(crash_density_label, 2, 0, Qt.AlignLeft) - grid.addWidget(self.crash_barrier_density, 2, 1, 1, 3) - - # Row 3: Crash Barrier Area - crash_area_label = QLabel("Crash Barrier Area (m2 ):") - crash_area_label.setStyleSheet("font-size: 11px; color: #555; border: none;") - crash_area_label.setMinimumWidth(180) - self.crash_barrier_area = QLineEdit() - self.crash_barrier_area.setValidator(QDoubleValidator(0.0, 10.0, 4)) - self.style_input_field(self.crash_barrier_area) - - grid.addWidget(crash_area_label, 3, 0, Qt.AlignLeft) - grid.addWidget(self.crash_barrier_area, 3, 1, 1, 3) - - # Row 4: Load Case - load_case_label = QLabel("Load Case:") - load_case_label.setStyleSheet("font-size: 11px; color: #555; border: none;") - load_case_label.setMinimumWidth(180) - self.crash_load_case = QComboBox() - self.crash_load_case.addItems(VALUES_LOAD_CASE) - self.crash_load_case.setCurrentText("Super-imposed Dead Load (SIDL)") - self.style_input_field(self.crash_load_case) - - grid.addWidget(load_case_label, 4, 0, Qt.AlignLeft) - grid.addWidget(self.crash_load_case, 4, 1, 1, 3) - - inputs_layout.addLayout(grid) - crash_layout.addWidget(inputs_group) - crash_layout.addStretch() - - self.input_tabs.addTab(crash_widget, "Crash Barrier") - - def create_railing_tab(self): - """Create the Railing tab""" - railing_widget = QWidget() - railing_widget.setStyleSheet("background-color: white;") - railing_layout = QVBoxLayout(railing_widget) - railing_layout.setContentsMargins(25, 25, 25, 25) - railing_layout.setSpacing(20) - - # --- Inputs Group --- - inputs_group = QGroupBox() - inputs_group.setStyleSheet(""" - QGroupBox { - background-color: white; - border: 2px solid #a0a0a0; - border-radius: 10px; - margin-top: 10px; - } - """) - inputs_layout = QVBoxLayout(inputs_group) - inputs_layout.setContentsMargins(20, 20, 20, 20) - inputs_layout.setSpacing(15) - - # Title - title_label = QLabel("Inputs:") - title_label.setStyleSheet("font-size: 12px; font-weight: bold; color: #000; border: none;") - inputs_layout.addWidget(title_label) - - # Create grid for inputs - grid = QGridLayout() - grid.setHorizontalSpacing(40) - grid.setVerticalSpacing(20) - grid.setColumnStretch(1, 1) - grid.setColumnStretch(3, 1) - - # Row 0: Railing Width - railing_width_label = QLabel("Railing Width (mm):") - railing_width_label.setStyleSheet("font-size: 11px; color: #555; border: none;") - railing_width_label.setMinimumWidth(180) - self.railing_width = QLineEdit() - self.railing_width.setValidator(QDoubleValidator(0.0, 1000.0, 1)) - self.style_input_field(self.railing_width) - self.railing_width.textChanged.connect(self.recalculate_girders) - - grid.addWidget(railing_width_label, 0, 0, Qt.AlignLeft) - grid.addWidget(self.railing_width, 0, 1, 1, 3) - - # Row 1: Railing Height - railing_height_label = QLabel("Railing Height (mm):") - railing_height_label.setStyleSheet("font-size: 11px; color: #555; border: none;") - railing_height_label.setMinimumWidth(180) - self.railing_height = QLineEdit() - self.railing_height.setValidator(QDoubleValidator(0.0, 2000.0, 1)) - self.style_input_field(self.railing_height) - self.railing_height.editingFinished.connect(self.validate_railing_height) - - grid.addWidget(railing_height_label, 1, 0, Qt.AlignLeft) - grid.addWidget(self.railing_height, 1, 1, 1, 3) - - # Row 2: Railing Load - railing_load_label = QLabel("Railing Load (kN/m)") - railing_load_label.setStyleSheet("font-size: 11px; color: #555; border: none;") - railing_load_label.setMinimumWidth(180) - self.railing_load = QLineEdit() - self.railing_load.setValidator(QDoubleValidator(0.0, 100.0, 2)) - self.style_input_field(self.railing_load) - - grid.addWidget(railing_load_label, 2, 0, Qt.AlignLeft) - grid.addWidget(self.railing_load, 2, 1, 1, 3) - - # Row 3: Load Case - load_case_label = QLabel("Load Case:") - load_case_label.setStyleSheet("font-size: 11px; color: #555; border: none;") - load_case_label.setMinimumWidth(180) - self.railing_load_case = QComboBox() - self.railing_load_case.addItems(VALUES_LOAD_CASE) - self.railing_load_case.setCurrentText("Super-imposed Dead Load (SIDL)") - self.style_input_field(self.railing_load_case) - - grid.addWidget(load_case_label, 3, 0, Qt.AlignLeft) - grid.addWidget(self.railing_load_case, 3, 1, 1, 3) - - inputs_layout.addLayout(grid) - railing_layout.addWidget(inputs_group) - railing_layout.addStretch() - - self.input_tabs.addTab(railing_widget, "Railing") - - def create_wearing_course_tab(self): - """Create the Wearing Course tab""" - wearing_widget = QWidget() - wearing_widget.setStyleSheet("background-color: white;") - wearing_layout = QVBoxLayout(wearing_widget) - wearing_layout.setContentsMargins(25, 25, 25, 25) - wearing_layout.setSpacing(20) - - # --- Inputs Group --- - inputs_group = QGroupBox() - inputs_group.setStyleSheet(""" - QGroupBox { - background-color: white; - border: 2px solid #a0a0a0; - border-radius: 10px; - margin-top: 10px; - } - """) - inputs_layout = QVBoxLayout(inputs_group) - inputs_layout.setContentsMargins(20, 20, 20, 20) - inputs_layout.setSpacing(15) - - # Title - title_label = QLabel("Inputs:") - title_label.setStyleSheet("font-size: 12px; font-weight: bold; color: #000; border: none;") - inputs_layout.addWidget(title_label) - - # Create grid for inputs - grid = QGridLayout() - grid.setHorizontalSpacing(40) - grid.setVerticalSpacing(20) - grid.setColumnStretch(1, 1) - grid.setColumnStretch(3, 1) - - # Row 0: Wearing Course Material - wc_material_label = QLabel("Wearing Course Material:") - wc_material_label.setStyleSheet("font-size: 11px; color: #555; border: none;") - wc_material_label.setMinimumWidth(180) - self.wc_material = QComboBox() - self.wc_material.addItems(VALUES_WEARING_COAT_MATERIAL) - self.style_input_field(self.wc_material) - - grid.addWidget(wc_material_label, 0, 0, Qt.AlignLeft) - grid.addWidget(self.wc_material, 0, 1, 1, 3) - - # Row 1: Wearing Coat Density - wc_density_label = QLabel("Wearing Coat Density (kN/m^3):") - wc_density_label.setStyleSheet("font-size: 11px; color: #555; border: none;") - wc_density_label.setMinimumWidth(180) - self.wc_density = QLineEdit() - self.wc_density.setValidator(QDoubleValidator(0.0, 100.0, 2)) - self.style_input_field(self.wc_density) - - grid.addWidget(wc_density_label, 1, 0, Qt.AlignLeft) - grid.addWidget(self.wc_density, 1, 1, 1, 3) - - # Row 2: Wearing Coat Thickness - wc_thickness_label = QLabel("Wearing Coat Thickness (mm):") - wc_thickness_label.setStyleSheet("font-size: 11px; color: #555; border: none;") - wc_thickness_label.setMinimumWidth(180) - self.wc_thickness = QLineEdit() - self.wc_thickness.setValidator(QDoubleValidator(0.0, 500.0, 1)) - self.style_input_field(self.wc_thickness) - - grid.addWidget(wc_thickness_label, 2, 0, Qt.AlignLeft) - grid.addWidget(self.wc_thickness, 2, 1, 1, 3) - - # Row 3: Load Case - load_case_label = QLabel("Load Case:") - load_case_label.setStyleSheet("font-size: 11px; color: #555; border: none;") - load_case_label.setMinimumWidth(180) - self.wc_load_case = QComboBox() - self.wc_load_case.addItems(VALUES_LOAD_CASE) - self.wc_load_case.setCurrentText("Dead Load of Wearing Course (DW)") - self.style_input_field(self.wc_load_case) - - grid.addWidget(load_case_label, 3, 0, Qt.AlignLeft) - grid.addWidget(self.wc_load_case, 3, 1, 1, 3) - - inputs_layout.addLayout(grid) - wearing_layout.addWidget(inputs_group) - wearing_layout.addStretch() - - self.input_tabs.addTab(wearing_widget, "Wearing Course") - - def create_lane_details_tab(self): - """Create the Lane Details tab""" - lane_widget = QWidget() - lane_widget.setStyleSheet("background-color: white;") - lane_layout = QVBoxLayout(lane_widget) - lane_layout.setContentsMargins(25, 25, 25, 25) - lane_layout.setSpacing(20) - - # Title - title_label = QLabel("Inputs:") - title_label.setStyleSheet("font-size: 12px; font-weight: bold; color: #000;") - lane_layout.addWidget(title_label) - - # Create grid for inputs - grid = QGridLayout() - grid.setHorizontalSpacing(40) - grid.setVerticalSpacing(20) - grid.setColumnStretch(1, 1) - - # Row 0: No. of Lanes - no_lanes_label = QLabel("No. of Lanes:") - no_lanes_label.setStyleSheet("font-size: 11px; color: #555;") - no_lanes_label.setMinimumWidth(150) - self.no_of_lanes = QComboBox() - self.no_of_lanes.addItems(VALUES_NO_OF_LANES) - self.style_input_field(self.no_of_lanes) - - grid.addWidget(no_lanes_label, 0, 0, Qt.AlignLeft) - grid.addWidget(self.no_of_lanes, 0, 1) - - # Row 1: Lane Width - lane_width_label = QLabel("Lane Width (m):") - lane_width_label.setStyleSheet("font-size: 11px; color: #555;") - lane_width_label.setMinimumWidth(150) - self.lane_width = QLineEdit() - self.lane_width.setValidator(QDoubleValidator(0.0, 20.0, 2)) - self.style_input_field(self.lane_width) - - grid.addWidget(lane_width_label, 1, 0, Qt.AlignLeft) - grid.addWidget(self.lane_width, 1, 1) - - lane_layout.addLayout(grid) - lane_layout.addStretch() - - self.input_tabs.addTab(lane_widget, "Lane Details") - - def update_footpath_value(self, footpath_value): - """Update visibility based on footpath selection""" - self.footpath_value = footpath_value - # Update visibility of footpath-related fields in Deck tab - if hasattr(self, 'footpath_width'): - self.footpath_width.setEnabled(footpath_value != "None") - self.footpath_thickness.setEnabled(footpath_value != "None") - self.recalculate_girders() # Recalculate when footpath changes - self.footpath_changed.emit(footpath_value) - - def get_overall_bridge_width(self): - """Calculate Overall Bridge Width = Carriageway + Footpath + Crash Barrier/Railing""" - try: - overall_width = self.carriageway_width - - # Add footpath width - if self.footpath_value != "None": - footpath_width = float(self.footpath_width.text()) if self.footpath_width.text() else 0 - # Count footpaths: "Single Sided" = 1, "Both" = 2 - num_footpaths = 2 if self.footpath_value == "Both" else (1 if self.footpath_value == "Single Sided" else 0) - overall_width += footpath_width * num_footpaths - - # Add crash barrier width - crash_barrier_width = float(self.crash_barrier_width.text()) if self.crash_barrier_width.text() else DEFAULT_CRASH_BARRIER_WIDTH - # Assuming crash barriers on both edges - overall_width += crash_barrier_width * 2 - - # Add railing width (if footpath present) - if self.footpath_value != "None": - railing_width = float(self.railing_width.text()) if self.railing_width.text() else DEFAULT_RAILING_WIDTH - # Railings on both sides if footpath exists - overall_width += railing_width * 2 - - return overall_width - except: - return self.carriageway_width - - def recalculate_girders(self): - """Recalculate based on the formula: (Overall Bridge Width - Deck Overhang) / Girder Spacing = No. of Girders""" - if self.updating_fields: - return - - try: - overall_width = self.get_overall_bridge_width() - - # Update the display field if it exists - if hasattr(self, 'overall_width_display'): - self.overall_width_display.setText(f"{overall_width:.3f}") - - spacing = float(self.girder_spacing.text()) if self.girder_spacing.text() else DEFAULT_GIRDER_SPACING - overhang = float(self.deck_overhang.text()) if self.deck_overhang.text() else DEFAULT_DECK_OVERHANG - - # Validate: spacing and overhang should be less than overall bridge width - if spacing >= overall_width or overhang >= overall_width: - self.no_of_girders.setText("") - return - - # Calculate: No. of Girders = (Overall Width - 2*Overhang) / Spacing + 1 - if spacing > 0: - no_girders = int(round((overall_width - 2 * overhang) / spacing)) + 1 - if no_girders >= 2: - self.updating_fields = True - self.no_of_girders.setText(str(no_girders)) - self.updating_fields = False - except: - pass - - def on_girder_spacing_changed(self): - """When user changes girder spacing, recalculate number of girders""" - if not self.updating_fields: - try: - overall_width = self.get_overall_bridge_width() - spacing_text = self.girder_spacing.text() - if spacing_text: - spacing = float(spacing_text) - if spacing >= overall_width: - QMessageBox.warning(self, "Invalid Girder Spacing", - f"Girder spacing ({spacing:.2f} m) must be less than overall bridge width ({overall_width:.2f} m).") - return - self.recalculate_girders() - except: - pass - - def on_deck_overhang_changed(self): - """When user changes deck overhang, recalculate number of girders""" - if not self.updating_fields: - try: - overall_width = self.get_overall_bridge_width() - overhang_text = self.deck_overhang.text() - if overhang_text: - overhang = float(overhang_text) - if overhang >= overall_width: - QMessageBox.warning(self, "Invalid Deck Overhang", - f"Deck overhang ({overhang:.2f} m) must be less than overall bridge width ({overall_width:.2f} m).") - return - self.recalculate_girders() - except: - pass - - def on_no_of_girders_changed(self): - """When user changes number of girders, recalculate girder spacing""" - if not self.updating_fields: - try: - no_girders_text = self.no_of_girders.text() - if no_girders_text: - no_girders = int(no_girders_text) - if no_girders < 2: - QMessageBox.warning(self, "Invalid Number of Girders", - "Number of girders must be at least 2.") - return - - overall_width = self.get_overall_bridge_width() - overhang = float(self.deck_overhang.text()) if self.deck_overhang.text() else DEFAULT_DECK_OVERHANG - - # Calculate spacing: Spacing = (Overall Width - 2*Overhang) / (No. of Girders - 1) - if no_girders > 1: - new_spacing = (overall_width - 2 * overhang) / (no_girders - 1) - self.updating_fields = True - self.girder_spacing.setText(f"{new_spacing:.3f}") - self.updating_fields = False - except: - pass - - def on_footpath_width_changed(self): - """When footpath width changes, recalculate girders""" - if not self.updating_fields: - self.recalculate_girders() - - def validate_footpath_width(self): - """Validate footpath width meets minimum IRC 5 requirements""" - try: - if self.footpath_width.text(): - width = float(self.footpath_width.text()) - if width < MIN_FOOTPATH_WIDTH: - QMessageBox.critical(self, "Footpath Width Error", - f"Footpath width must be at least {MIN_FOOTPATH_WIDTH} m as per IRC 5 Clause 104.3.6.") - except: - pass - - def validate_railing_height(self): - """Validate railing height meets minimum IRC 5 requirements""" - try: - if self.railing_height.text(): - height = float(self.railing_height.text()) - if height < MIN_RAILING_HEIGHT: - QMessageBox.critical(self, "Railing Height Error", - f"Railing height must be at least {MIN_RAILING_HEIGHT} m as per IRC 5 Clauses 109.7.2.3 and 109.7.2.4.") - except: - pass - - def validate_safety_kerb_width(self): - """Validate safety kerb width meets minimum IRC 5 requirements""" - try: - if self.safety_kerb_width.text(): - width = float(self.safety_kerb_width.text()) - if width < MIN_SAFETY_KERB_WIDTH: - QMessageBox.critical(self, "Safety Kerb Width Error", - f"Safety kerb width must be at least {MIN_SAFETY_KERB_WIDTH} m (750 mm) as per IRC 5 Clause 101.41.") - except: - pass - - def update_footpath_thickness(self): - """Pre-fill footpath thickness with deck thickness""" - if self.deck_thickness.text() and not self.footpath_thickness.text(): - self.footpath_thickness.setText(self.deck_thickness.text()) - - def on_crash_barrier_type_changed(self, barrier_type): - """Warn if flexible/semi-rigid barrier without footpath""" - if (barrier_type in ["Flexible", "Semi-Rigid"]) and (self.footpath_value == "None"): - QMessageBox.critical(self, "Crash Barrier Type Not Permitted", - f"{barrier_type} crash barriers are not permitted on bridges without an outer footpath per IRC 5 Clause 109.6.4.") - - -class SectionPropertiesTab(QWidget): - """Sub-tab for Section Properties with custom navigation layout.""" - - def __init__(self, parent=None): - super().__init__(parent) - self.nav_buttons = [] - self.init_ui() - - def init_ui(self): - """Initialize styled navigation and content panels.""" - self.setStyleSheet("background-color: #f0f0f0;") - main_layout = QVBoxLayout(self) - main_layout.setContentsMargins(10, 10, 10, 10) - main_layout.setSpacing(10) - - # Top navigation bar (horizontal) - nav_bar = QWidget() - nav_bar.setStyleSheet("background-color: transparent;") - nav_bar_layout = QHBoxLayout(nav_bar) - nav_bar_layout.setContentsMargins(0, 0, 0, 0) - nav_bar_layout.setSpacing(0) - - main_layout.addWidget(nav_bar) - - # Content frame - content_frame = QFrame() - content_frame.setObjectName("sectionContentFrame") - content_frame.setStyleSheet(""" - QFrame#sectionContentFrame { - background-color: #f0f0f0; - border: none; - } - """) - content_inner_layout = QVBoxLayout(content_frame) - content_inner_layout.setContentsMargins(0, 0, 0, 0) - content_inner_layout.setSpacing(0) - - self.stack = QStackedWidget() - self.stack.setObjectName("sectionStack") - self.stack.setStyleSheet("QStackedWidget#sectionStack { background-color: transparent; }") - content_inner_layout.addWidget(self.stack) - - main_layout.addWidget(content_frame, 1) - - sections = [ - ("Girder Details:", GirderDetailsTab), - ("Stiffener Details:", StiffenerDetailsTab), - ("Cross-Bracing Details:", CrossBracingDetailsTab), - ("End Diaphragm Details:", EndDiaphragmDetailsTab), - ] - - for i, (label, widget_class) in enumerate(sections): - btn = QPushButton(label) - btn.setObjectName("sectionNavBtn") - btn.setCheckable(True) - btn.setStyleSheet(""" - QPushButton#sectionNavBtn { - background-color: white; - color: #333; - border: 1px solid #b0b0b0; - border-right: none; - padding: 10px 20px; - text-align: center; - font-size: 11px; - font-weight: normal; - min-height: 30px; - } - QPushButton#sectionNavBtn:first { - border-top-left-radius: 5px; - border-bottom-left-radius: 5px; - } - QPushButton#sectionNavBtn:last { - border-right: 1px solid #b0b0b0; - border-top-right-radius: 5px; - border-bottom-right-radius: 5px; - } - QPushButton#sectionNavBtn:checked { - background-color: #90AF13; - color: white; - font-weight: bold; - border: 1px solid #90AF13; - } - QPushButton#sectionNavBtn:hover:!checked { - background-color: #f5f5f5; - } - """) - btn.clicked.connect(lambda checked, idx=i: self.switch_section(idx)) - self.nav_buttons.append(btn) - nav_bar_layout.addWidget(btn) - - section_widget = widget_class() - self.stack.addWidget(section_widget) - - if self.nav_buttons: - self.nav_buttons[0].setChecked(True) - self.stack.setCurrentIndex(0) - - def switch_section(self, index): - """Switch the stacked widget page and update navigation states.""" - self.stack.setCurrentIndex(index) - for btn_index, button in enumerate(self.nav_buttons): - button.setChecked(btn_index == index) - - -class GirderDetailsTab(QWidget): - """Tab for Girder Details""" - - def __init__(self, parent=None): - super().__init__(parent) - self.init_ui() - - def init_ui(self): - """Initialize the UI""" - main_layout = QVBoxLayout(self) - main_layout.setContentsMargins(0, 0, 0, 0) - main_layout.setSpacing(0) - - scroll = QScrollArea() - scroll.setWidgetResizable(True) - scroll.setFrameShape(QFrame.NoFrame) - scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) - scroll.setStyleSheet( - "QScrollArea { border: none; background: transparent; }" - "QScrollArea > QWidget > QWidget { background: transparent; }" - ) - main_layout.addWidget(scroll) - - container = QWidget() - scroll.setWidget(container) - - container_layout = QVBoxLayout(container) - container_layout.setContentsMargins(10, 10, 10, 10) - container_layout.setSpacing(10) - - # Common label style - label_style = "QLabel { color: #333333; font-size: 11px; background-color: transparent; }" - title_style = "QLabel { color: #333333; font-weight: bold; font-size: 12px; margin-bottom: 10px; background-color: transparent; }" - - # --- Top Section --- - top_group = QGroupBox() - top_group.setStyleSheet(""" - QGroupBox { - background-color: white; - border: 1px solid #b0b0b0; - border-radius: 8px; - } - """) - top_layout = QGridLayout(top_group) - top_layout.setContentsMargins(15, 15, 15, 15) - top_layout.setHorizontalSpacing(20) - top_layout.setVerticalSpacing(15) - - # Row 0 - lbl_girder = QLabel("Select Girder:") - lbl_girder.setStyleSheet(label_style) - top_layout.addWidget(lbl_girder, 0, 0) - - self.select_girder = QComboBox() - self.select_girder.addItems(["Girder 1", "Girder 2", "Girder 3"]) # Placeholder items - apply_field_style(self.select_girder) - top_layout.addWidget(self.select_girder, 0, 1) - - # Spacer - top_layout.addItem(QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum), 0, 2) - - # Row 1 - lbl_span = QLabel("Span:") - lbl_span.setStyleSheet(label_style) - top_layout.addWidget(lbl_span, 1, 0) - - self.span_combo = QComboBox() - self.span_combo.addItems(["Custom", "Span 1", "Span 2"]) - apply_field_style(self.span_combo) - top_layout.addWidget(self.span_combo, 1, 1) - - lbl_member_id = QLabel("Member ID:") - lbl_member_id.setStyleSheet(label_style) - top_layout.addWidget(lbl_member_id, 1, 3) - - self.member_id = QLineEdit("G1-1") - apply_field_style(self.member_id) - top_layout.addWidget(self.member_id, 1, 4) - - # Row 2 - lbl_dist = QLabel("Distance from left edge (m):") - lbl_dist.setStyleSheet(label_style) - top_layout.addWidget(lbl_dist, 2, 0) - - dist_layout = QHBoxLayout() - dist_layout.setSpacing(10) - - dist_start_layout = QVBoxLayout() - self.dist_start = QLineEdit() - apply_field_style(self.dist_start) - self.dist_start.setFixedWidth(80) - dist_start_layout.addWidget(self.dist_start) - - dist_start_label = QLabel("Start") - dist_start_label.setAlignment(Qt.AlignCenter) - dist_start_label.setStyleSheet("font-size: 10px; color: #555555;") - dist_start_layout.addWidget(dist_start_label) - dist_layout.addLayout(dist_start_layout) - - dist_end_layout = QVBoxLayout() - self.dist_end = QLineEdit() - apply_field_style(self.dist_end) - self.dist_end.setFixedWidth(80) - dist_end_layout.addWidget(self.dist_end) - - dist_end_label = QLabel("End") - dist_end_label.setAlignment(Qt.AlignCenter) - dist_end_label.setStyleSheet("font-size: 10px; color: #555555;") - dist_end_layout.addWidget(dist_end_label) - dist_layout.addLayout(dist_end_layout) - - top_layout.addLayout(dist_layout, 2, 1) - - lbl_length = QLabel("Length (m):") - lbl_length.setStyleSheet(label_style) - top_layout.addWidget(lbl_length, 2, 3) - - self.length_input = QLineEdit() - apply_field_style(self.length_input) - top_layout.addWidget(self.length_input, 2, 4) - - container_layout.addWidget(top_group) - - # --- Main Content (Left/Right) --- - content_layout = QHBoxLayout() - content_layout.setSpacing(10) - - # Left Column - left_column = QVBoxLayout() - left_column.setSpacing(10) - - # Section Inputs Group - inputs_group = QGroupBox() - inputs_group.setStyleSheet(""" - QGroupBox { - background-color: white; - border: 1px solid #b0b0b0; - border-radius: 8px; - padding-top: 10px; - } - """) - inputs_layout = QVBoxLayout(inputs_group) - inputs_layout.setContentsMargins(15, 15, 15, 15) - - inputs_title = QLabel("Section Inputs:") - inputs_title.setStyleSheet(title_style) - inputs_layout.addWidget(inputs_title) - - inputs_grid = QGridLayout() - inputs_grid.setHorizontalSpacing(15) - inputs_grid.setVerticalSpacing(15) - inputs_grid.setColumnStretch(1, 1) - - row = 0 - lbl_design = QLabel("Design:") - lbl_design.setStyleSheet(label_style) - inputs_grid.addWidget(lbl_design, row, 0) - - self.design_combo = QComboBox() - self.design_combo.addItems(["Customized", "Standard"]) - apply_field_style(self.design_combo) - inputs_grid.addWidget(self.design_combo, row, 1) - row += 1 - - lbl_type = QLabel("Type:") - lbl_type.setStyleSheet(label_style) - inputs_grid.addWidget(lbl_type, row, 0) - - self.type_combo = QComboBox() - self.type_combo.addItems(["Welded", "Rolled"]) - apply_field_style(self.type_combo) - inputs_grid.addWidget(self.type_combo, row, 1) - row += 1 - - lbl_symmetry = QLabel("Symmetry:") - lbl_symmetry.setStyleSheet(label_style) - inputs_grid.addWidget(lbl_symmetry, row, 0) - - self.symmetry_combo = QComboBox() - self.symmetry_combo.addItems(["Girder Symmetric", "Asymmetric"]) - apply_field_style(self.symmetry_combo) - inputs_grid.addWidget(self.symmetry_combo, row, 1) - row += 1 - - lbl_depth = QLabel("Total Depth (mm):") - lbl_depth.setStyleSheet(label_style) - inputs_grid.addWidget(lbl_depth, row, 0) - - self.total_depth = QLineEdit() - apply_field_style(self.total_depth) - inputs_grid.addWidget(self.total_depth, row, 1) - row += 1 - - lbl_web_thick = QLabel("Web Thickness (mm):") - lbl_web_thick.setStyleSheet(label_style) - inputs_grid.addWidget(lbl_web_thick, row, 0) - - self.web_thickness = QComboBox() - self.web_thickness.addItems(["All", "Custom"]) - apply_field_style(self.web_thickness) - inputs_grid.addWidget(self.web_thickness, row, 1) - row += 1 - - lbl_top_width = QLabel("Width of Top Flange (mm):") - lbl_top_width.setStyleSheet(label_style) - inputs_grid.addWidget(lbl_top_width, row, 0) - - self.top_flange_width = QLineEdit() - apply_field_style(self.top_flange_width) - inputs_grid.addWidget(self.top_flange_width, row, 1) - row += 1 - - lbl_top_thick = QLabel("Top Flange Thickness (mm):") - lbl_top_thick.setStyleSheet(label_style) - inputs_grid.addWidget(lbl_top_thick, row, 0) - - self.top_flange_thickness = QComboBox() - self.top_flange_thickness.addItems(["All", "Custom"]) - apply_field_style(self.top_flange_thickness) - inputs_grid.addWidget(self.top_flange_thickness, row, 1) - row += 1 - - lbl_bot_width = QLabel("Width of Bottom Flange (mm):") - lbl_bot_width.setStyleSheet(label_style) - inputs_grid.addWidget(lbl_bot_width, row, 0) - - self.bottom_flange_width = QLineEdit() - apply_field_style(self.bottom_flange_width) - inputs_grid.addWidget(self.bottom_flange_width, row, 1) - row += 1 - - lbl_bot_thick = QLabel("Bottom Flange Thickness (mm):") - lbl_bot_thick.setStyleSheet(label_style) - inputs_grid.addWidget(lbl_bot_thick, row, 0) - - self.bottom_flange_thickness = QComboBox() - self.bottom_flange_thickness.addItems(["All", "Custom"]) - apply_field_style(self.bottom_flange_thickness) - inputs_grid.addWidget(self.bottom_flange_thickness, row, 1) - row += 1 - - inputs_layout.addLayout(inputs_grid) - left_column.addWidget(inputs_group) - - # Restraints Group - restraints_group = QGroupBox() - restraints_group.setStyleSheet(""" - QGroupBox { - background-color: white; - border: 1px solid #b0b0b0; - border-radius: 8px; - } - """) - restraints_layout = QGridLayout(restraints_group) - restraints_layout.setContentsMargins(15, 15, 15, 15) - restraints_layout.setHorizontalSpacing(15) - restraints_layout.setVerticalSpacing(15) - restraints_layout.setColumnStretch(1, 1) - - r_row = 0 - lbl_torsion = QLabel("Torsional Restraint:") - lbl_torsion.setStyleSheet(label_style) - restraints_layout.addWidget(lbl_torsion, r_row, 0) - - self.torsional_restraint = QComboBox() - self.torsional_restraint.addItems(["Fully Restrained", "Free"]) - apply_field_style(self.torsional_restraint) - restraints_layout.addWidget(self.torsional_restraint, r_row, 1) - r_row += 1 - - lbl_warping = QLabel("Warping Restraint:") - lbl_warping.setStyleSheet(label_style) - restraints_layout.addWidget(lbl_warping, r_row, 0) - - self.warping_restraint = QComboBox() - self.warping_restraint.addItems(["Both flanges fully restrained", "Free"]) - apply_field_style(self.warping_restraint) - restraints_layout.addWidget(self.warping_restraint, r_row, 1) - r_row += 1 - - lbl_web_type = QLabel("Web Type*:") - lbl_web_type.setStyleSheet(label_style) - restraints_layout.addWidget(lbl_web_type, r_row, 0) - - self.web_type = QComboBox() - self.web_type.addItems(["Thin Web with ITS", "Stocky Web"]) - apply_field_style(self.web_type) - restraints_layout.addWidget(self.web_type, r_row, 1) - - left_column.addWidget(restraints_group) - content_layout.addLayout(left_column, 1) - - # Right Column - right_column = QVBoxLayout() - right_column.setSpacing(10) - - # Image Preview - image_frame = QFrame() - image_frame.setStyleSheet(""" - QFrame { - background-color: #d9d9d9; - border: 1px solid #b0b0b0; - border-radius: 8px; - } - """) - image_frame.setMinimumHeight(200) - image_layout = QVBoxLayout(image_frame) - image_label = QLabel("Dynamic Image") - image_label.setStyleSheet("color: #333333; font-weight: bold;") - image_label.setAlignment(Qt.AlignCenter) - image_layout.addWidget(image_label) - - right_column.addWidget(image_frame) - - # Section Properties Group - props_group = QGroupBox() - props_group.setStyleSheet(""" - QGroupBox { - background-color: white; - border: 1px solid #b0b0b0; - border-radius: 8px; - padding-top: 10px; - } - """) - props_layout = QVBoxLayout(props_group) - props_layout.setContentsMargins(15, 15, 15, 15) - - props_title = QLabel("Section Properties:") - props_title.setStyleSheet(title_style) - props_layout.addWidget(props_title) - - props_grid = QGridLayout() - props_grid.setHorizontalSpacing(15) - props_grid.setVerticalSpacing(10) - props_grid.setColumnStretch(1, 1) - - properties = [ - ("Mass, M (Kg/m)", ""), - ("Sectional Area, a (cm2)", ""), - ("2nd Moment of Area, Iz (cm4)", ""), - ("2nd Moment of Area, Iv (cm4)", ""), - ("Radius of Gyration, rz (cm)", ""), - ("Radius of Gyration, rv (cm)", ""), - ("Elastic Modulus, Zz (cm3)", ""), - ("Elastic Modulus, Zv (cm3)", ""), - ("Plastic Modulus, Zuz (cm3)", ""), - ("Plastic Modulus, Zuv (cm3)", ""), - ("Torsion Constant, It (cm4)", ""), - ("Warping Constant, Iw (cm6)", ""), - ] - - self.prop_inputs = {} - for i, (label_text, _) in enumerate(properties): - lbl_prop = QLabel(label_text) - lbl_prop.setStyleSheet(label_style) - props_grid.addWidget(lbl_prop, i, 0) - - line_edit = QLineEdit() - line_edit.setReadOnly(True) - apply_field_style(line_edit) - props_grid.addWidget(line_edit, i, 1) - self.prop_inputs[label_text] = line_edit - - props_layout.addLayout(props_grid) - right_column.addWidget(props_group) - - content_layout.addLayout(right_column, 1) - container_layout.addLayout(content_layout) - container_layout.addStretch() - - -class StiffenerDetailsTab(QWidget): - """Tab for Stiffener Details""" - - def __init__(self, parent=None): - super().__init__(parent) - self.init_ui() - - def init_ui(self): - main_layout = QVBoxLayout(self) - main_layout.setContentsMargins(0, 0, 0, 0) - main_layout.setSpacing(0) - - scroll = QScrollArea() - scroll.setWidgetResizable(True) - scroll.setFrameShape(QFrame.NoFrame) - scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) - scroll.setStyleSheet( - "QScrollArea { border: none; background: transparent; }" - "QScrollArea > QWidget > QWidget { background: transparent; }" - ) - main_layout.addWidget(scroll) - - container = QWidget() - scroll.setWidget(container) - - container_layout = QVBoxLayout(container) - container_layout.setContentsMargins(0, 0, 0, 0) - container_layout.setSpacing(0) - - form_frame = QFrame() - form_frame.setStyleSheet("QFrame { background: transparent; }") - self.form_layout = QGridLayout(form_frame) - self.form_layout.setContentsMargins(0, 0, 0, 0) - self.form_layout.setHorizontalSpacing(28) - self.form_layout.setVerticalSpacing(20) - self.form_layout.setColumnMinimumWidth(0, 240) - self.form_layout.setColumnStretch(1, 1) - container_layout.addWidget(form_frame) - container_layout.addStretch() - - row = 0 - self.method_combo = QComboBox() - self.method_combo.addItems(VALUES_STIFFENER_DESIGN) - apply_field_style(self.method_combo) - row = self.add_row(row, "Stiffener design method:", self.method_combo) - - self.thick_combo = QComboBox() - self.thick_combo.addItems(["Optimized", "All"]) - apply_field_style(self.thick_combo) - row = self.add_row(row, "Stiffener Plate Thickness (mm):", self.thick_combo) - - self.spacing_field = OptimizableField("Stiffener Spacing") - self.spacing_field.mode_combo.clear() - self.spacing_field.mode_combo.addItems(["Optimized", "Customized"]) - self.spacing_field.on_mode_changed(self.spacing_field.mode_combo.currentText()) - self.prepare_optimizable_field(self.spacing_field) - row = self.add_row(row, "Stiffener Spacing (mm):", self.spacing_field) - - self.long_req_combo = QComboBox() - self.long_req_combo.addItems(VALUES_YES_NO) - apply_field_style(self.long_req_combo) - row = self.add_row(row, "Longitudinal stiffener requirement:", self.long_req_combo) - - self.long_thick_combo = QComboBox() - self.long_thick_combo.addItems(["Optimized", "All"]) - self.long_thick_combo.setEnabled(False) - apply_field_style(self.long_thick_combo) - self.add_row(row, "Longitudinal stiffener thickness:", self.long_thick_combo) - - self.long_req_combo.currentTextChanged.connect(self.on_long_req_changed) - - def create_label(self, text): - label = QLabel(text) - label.setStyleSheet("font-size: 13px; color: #2f2f2f; font-weight: 600;") - return label - - def add_row(self, row, text, widget): - label = self.create_label(text) - widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) - self.form_layout.addWidget(label, row, 0, Qt.AlignVCenter) - self.form_layout.addWidget(widget, row, 1) - return row + 1 - - def prepare_optimizable_field(self, field): - field.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) - apply_field_style(field.mode_combo) - apply_field_style(field.input_field) - - def on_long_req_changed(self, text): - """Enable/disable longitudinal stiffener thickness based on requirement""" - self.long_thick_combo.setEnabled(text == "Yes") - - -class CrossBracingDetailsTab(QWidget): - """Tab for Cross-Bracing Details""" - - def __init__(self, parent=None): - super().__init__(parent) - self.init_ui() - - def init_ui(self): - main_layout = QVBoxLayout(self) - main_layout.setContentsMargins(0, 0, 0, 0) - main_layout.setSpacing(0) - - scroll = QScrollArea() - scroll.setWidgetResizable(True) - scroll.setFrameShape(QFrame.NoFrame) - scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) - scroll.setStyleSheet( - "QScrollArea { border: none; background: transparent; }" - "QScrollArea > QWidget > QWidget { background: transparent; }" - ) - main_layout.addWidget(scroll) - - container = QWidget() - scroll.setWidget(container) - - container_layout = QVBoxLayout(container) - container_layout.setContentsMargins(0, 0, 0, 0) - container_layout.setSpacing(0) - - form_frame = QFrame() - form_frame.setStyleSheet("QFrame { background: transparent; }") - self.form_layout = QGridLayout(form_frame) - self.form_layout.setContentsMargins(0, 0, 0, 0) - self.form_layout.setHorizontalSpacing(28) - self.form_layout.setVerticalSpacing(20) - self.form_layout.setColumnMinimumWidth(0, 210) - self.form_layout.setColumnStretch(1, 1) - container_layout.addWidget(form_frame) - container_layout.addStretch() - - row = 0 - self.type_combo = QComboBox() - self.type_combo.addItems(VALUES_CROSS_BRACING_TYPE) - apply_field_style(self.type_combo) - row = self.add_row(row, "Type of Bracing:", self.type_combo) - - self.section_combo = QComboBox() - self.section_combo.addItems([ - "Select Section", - "ISA 50x50x6", "ISA 65x65x6", "ISA 75x75x6", "ISA 90x90x8", - "ISA 100x100x8", "ISA 110x110x10", "ISA 130x130x10", - "2-ISA 50x50x6 (LL)", "2-ISA 65x65x6 (LL)", "2-ISA 75x75x6 (LL)", - "2-ISA 50x50x6 (SL)", "2-ISA 65x65x6 (SL)", "2-ISA 75x75x6 (SL)", - "ISMC 75", "ISMC 100", "ISMC 125", "ISMC 150", - "2-ISMC 75", "2-ISMC 100", "2-ISMC 125" - ]) - apply_field_style(self.section_combo) - row = self.add_row(row, "Bracing Section:", self.section_combo) - - self.bracket_combo = QComboBox() - self.bracket_combo.addItems([ - "Select Section", - "ISA 50x50x6", "ISA 65x65x6", "ISA 75x75x6", "ISA 90x90x8", - "ISA 100x100x8", "ISA 110x110x10", - "2-ISA 50x50x6 (LL)", "2-ISA 65x65x6 (LL)", "2-ISA 75x75x6 (LL)", - "2-ISA 50x50x6 (SL)", "2-ISA 65x65x6 (SL)", "2-ISA 75x75x6 (SL)", - "ISMC 75", "ISMC 100", "ISMC 125", - "2-ISMC 75", "2-ISMC 100" - ]) - self.bracket_combo.setEnabled(False) - apply_field_style(self.bracket_combo) - row = self.add_row(row, "Bracket Section:", self.bracket_combo) - - self.spacing_input = QLineEdit() - self.spacing_input.setPlaceholderText("Enter spacing in mm") - self.spacing_input.setValidator(QDoubleValidator(0, 100000, 2)) - apply_field_style(self.spacing_input) - self.add_row(row, "Spacing (mm):", self.spacing_input) - - self.type_combo.currentTextChanged.connect(self.on_bracing_type_changed) - - def create_label(self, text): - label = QLabel(text) - label.setStyleSheet("font-size: 13px; color: #2f2f2f; font-weight: 600;") - return label - - def add_row(self, row, text, widget): - label = self.create_label(text) - widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) - self.form_layout.addWidget(label, row, 0, Qt.AlignVCenter) - self.form_layout.addWidget(widget, row, 1) - return row + 1 - - def on_bracing_type_changed(self, text): - """Enable/disable bracket section based on bracing type""" - has_bracket = "bracket" in text.lower() - self.bracket_combo.setEnabled(has_bracket) - - -class EndDiaphragmDetailsTab(QWidget): - """Tab for End Diaphragm Details""" - - def __init__(self, parent=None): - super().__init__(parent) - self.plate_rows = [] - self.init_ui() - - def init_ui(self): - main_layout = QVBoxLayout(self) - main_layout.setContentsMargins(0, 0, 0, 0) - main_layout.setSpacing(0) - - scroll = QScrollArea() - scroll.setWidgetResizable(True) - scroll.setFrameShape(QFrame.NoFrame) - scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) - scroll.setStyleSheet( - "QScrollArea { border: none; background: transparent; }" - "QScrollArea > QWidget > QWidget { background: transparent; }" - ) - main_layout.addWidget(scroll) - - container = QWidget() - scroll.setWidget(container) - - container_layout = QVBoxLayout(container) - container_layout.setContentsMargins(0, 0, 0, 0) - container_layout.setSpacing(0) - - form_frame = QFrame() - form_frame.setStyleSheet("QFrame { background: transparent; }") - self.form_layout = QGridLayout(form_frame) - self.form_layout.setContentsMargins(0, 0, 0, 0) - self.form_layout.setHorizontalSpacing(28) - self.form_layout.setVerticalSpacing(20) - self.form_layout.setColumnMinimumWidth(0, 230) - self.form_layout.setColumnStretch(1, 1) - container_layout.addWidget(form_frame) - container_layout.addStretch() - - row = 0 - self.type_combo = QComboBox() - self.type_combo.addItems(VALUES_END_DIAPHRAGM_TYPE) - apply_field_style(self.type_combo) - self.form_layout.addWidget(self.create_label("Type of Section:"), row, 0, Qt.AlignVCenter) - self.form_layout.addWidget(self.type_combo, row, 1) - row += 1 - - self.is_section_label = self.create_label("Select IS Beam Section:") - self.is_beam_combo = QComboBox() - self.is_beam_combo.addItems([ - "Select Section", - "ISMB 100", "ISMB 125", "ISMB 150", "ISMB 175", "ISMB 200", - "ISMB 225", "ISMB 250", "ISMB 300", "ISMB 350", "ISMB 400", - "ISWB 150", "ISWB 175", "ISWB 200", "ISWB 225", "ISWB 250", - "ISWB 300", "ISWB 350", "ISWB 400" - ]) - apply_field_style(self.is_beam_combo) - self.form_layout.addWidget(self.is_section_label, row, 0, Qt.AlignVCenter) - self.form_layout.addWidget(self.is_beam_combo, row, 1) - row += 1 - - self.top_width_field = OptimizableField("Top Flange Width") - self.prepare_optimizable_field(self.top_width_field) - row = self.add_plate_row(row, "Top Flange Width (mm):", self.top_width_field) - - self.top_thick_field = OptimizableField("Top Flange Thickness") - self.prepare_optimizable_field(self.top_thick_field) - row = self.add_plate_row(row, "Top Flange Thickness (mm):", self.top_thick_field) - - self.bottom_width_field = OptimizableField("Bottom Flange Width") - self.prepare_optimizable_field(self.bottom_width_field) - row = self.add_plate_row(row, "Bottom Flange Width (mm):", self.bottom_width_field) - - self.bottom_thick_field = OptimizableField("Bottom Flange Thickness") - self.prepare_optimizable_field(self.bottom_thick_field) - row = self.add_plate_row(row, "Bottom Flange Thickness (mm):", self.bottom_thick_field) - - self.depth_field = OptimizableField("Depth of Section") - self.prepare_optimizable_field(self.depth_field) - row = self.add_plate_row(row, "Depth of Section (mm):", self.depth_field) - - self.web_thick_field = OptimizableField("Web Thickness") - self.prepare_optimizable_field(self.web_thick_field) - row = self.add_plate_row(row, "Web Thickness (mm):", self.web_thick_field) - - self.spacing_input = QLineEdit() - self.spacing_input.setPlaceholderText("Enter spacing in mm") - self.spacing_input.setValidator(QDoubleValidator(0, 100000, 2)) - apply_field_style(self.spacing_input) - self.spacing_label = self.create_label("Spacing (mm):") - self.form_layout.addWidget(self.spacing_label, row, 0, Qt.AlignVCenter) - self.form_layout.addWidget(self.spacing_input, row, 1) - - self.type_combo.currentTextChanged.connect(self.on_type_changed) - self.on_type_changed(self.type_combo.currentText()) - - def create_label(self, text): - label = QLabel(text) - label.setStyleSheet("font-size: 13px; color: #2f2f2f; font-weight: 600;") - return label - - def prepare_optimizable_field(self, field): - field.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) - apply_field_style(field.mode_combo) - apply_field_style(field.input_field) - - def add_plate_row(self, row, text, widget): - label = self.create_label(text) - widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) - self.form_layout.addWidget(label, row, 0, Qt.AlignVCenter) - self.form_layout.addWidget(widget, row, 1) - self.plate_rows.append((label, widget)) - return row + 1 - - def on_type_changed(self, text): - """Show/hide sections based on diaphragm type""" - is_same = text == "Same as cross-bracing" - is_rolled = text == "Rolled Beam Section" - - self.is_section_label.setVisible(is_rolled) - self.is_beam_combo.setVisible(is_rolled) - - show_plate = text == "Plate Girder Section" - for label, widget in self.plate_rows: - label.setVisible(show_plate) - widget.setVisible(show_plate) - - if is_same: - self.spacing_input.setEnabled(False) - self.spacing_label.setEnabled(False) - self.spacing_input.clear() - else: - self.spacing_input.setEnabled(True) - self.spacing_label.setEnabled(True) - - -class AdditionalInputsWidget(QWidget): - """Main widget for Additional Inputs with tabbed interface""" - - def __init__(self, footpath_value="None", carriageway_width=7.5, parent=None): - super().__init__(parent) - self.footpath_value = footpath_value - self.carriageway_width = carriageway_width - self.init_ui() - - def init_ui(self): - self.setAttribute(Qt.WA_StyledBackground, True) - self.setStyleSheet("background-color: white;") - main_layout = QVBoxLayout(self) - main_layout.setContentsMargins(0, 0, 0, 0) - main_layout.setSpacing(0) - - # Header - header_widget = QWidget() - header_widget.setStyleSheet("background-color: white; border-bottom: 1px solid #d0d0d0;") - header_widget.setMinimumHeight(50) - header_layout = QHBoxLayout(header_widget) - header_layout.setContentsMargins(20, 0, 20, 0) - - title = QLabel("Additional Inputs") - title.setStyleSheet("font-size: 16px; font-weight: bold; color: #333;") - header_layout.addWidget(title) - - main_layout.addWidget(header_widget) - - # Main tab widget - self.tabs = QTabWidget() - self.tabs.setStyleSheet(""" - QTabWidget::pane { - border: none; - background: #ffffff; - margin-top: -1px; - } - QTabBar { - background: #ffffff; - } - QTabBar::tab { - background: #e9e9e9; - color: #3a3a3a; - border: 1px solid #d1d1d1; - border-bottom: none; - padding: 10px 22px; - margin-right: 4px; - border-top-left-radius: 10px; - border-top-right-radius: 10px; - font-weight: 500; - } - QTabBar::tab:selected { - background: #90af13; - color: #ffffff; - border: 1px solid #90af13; - border-bottom: none; - } - QTabBar::tab:hover:!selected { - background: #f5f5f5; - } - QTabWidget { - background-color: white; - } - """) - - # Sub-Tab 1: Typical Section Details - self.bridge_geometry_tab = BridgeGeometryTab(self.footpath_value, self.carriageway_width) - self.tabs.addTab(self.bridge_geometry_tab, "Typical Section Details") - - # Sub-Tab 2: Member Properties - self.section_properties_tab = SectionPropertiesTab() - self.tabs.addTab(self.section_properties_tab, "Member Properties") - - # Sub-Tab 3: Loading - loading_tab = self.create_placeholder_tab( - "Loading", - "This tab will contain:\n\n" + - "• Dead Load (Self Weight, Wearing Coat, etc.)\n" + - "• Live Load (IRC Vehicles, Custom Loads)\n" + - "• Lateral Load (Wind, Seismic)\n\n" + - "Implementation in progress..." - ) - self.tabs.addTab(loading_tab, "Loading") - - # Sub-Tab 4: Support Conditions - support_tab = self.create_placeholder_tab( - "Support Conditions", - "This tab will contain:\n\n" + - "• Left Support (Fixed/Pinned)\n" + - "• Right Support (Fixed/Pinned)\n" + - "• Bearing Length (mm)\n\n" + - "Note: If bearing length is 0, the end bearing\n" + - "stiffener will not be designed.\n\n" + - "Implementation in progress..." - ) - self.tabs.addTab(support_tab, "Support Conditions") - - # Sub-Tab 5: Design Options - shear_connection_tab = self.create_placeholder_tab( - "Design Options", - "This tab will contain:\n\n" + - "• Shear Connector Type\n" + - "• Connector Size and Spacing\n" + - "• Connection Details\n\n" + - "Implementation in progress..." - ) - self.tabs.addTab(shear_connection_tab, "Design Options") - - # Sub-Tab 6: Design Options (Cont.) - analysis_design_tab = self.create_placeholder_tab( - "Design Options (Cont.)", - "This tab will contain:\n\n" + - "• Analysis Method\n" + - "• Design Code Options\n" + - "• Safety Factors\n" + - "• Other Design Parameters\n\n" + - "Implementation in progress..." - ) - self.tabs.addTab(analysis_design_tab, "Design Options (Cont.)") - - main_layout.addWidget(self.tabs) - - def create_placeholder_tab(self, title, description): - """Create a styled placeholder tab with title and description""" - widget = QWidget() - widget.setStyleSheet("background-color: white;") - - layout = QVBoxLayout(widget) - layout.setAlignment(Qt.AlignCenter) - layout.setContentsMargins(40, 40, 40, 40) - - # Icon or visual indicator - icon_label = QLabel("🚧") - icon_label.setStyleSheet("font-size: 48px;") - icon_label.setAlignment(Qt.AlignCenter) - layout.addWidget(icon_label) - - # Title - title_label = QLabel(title) - title_label.setStyleSheet(""" - font-size: 18px; - font-weight: bold; - color: #333; - margin-top: 20px; - margin-bottom: 10px; - """) - title_label.setAlignment(Qt.AlignCenter) - layout.addWidget(title_label) - - # Status - status_label = QLabel("Under Development") - status_label.setStyleSheet(""" - font-size: 14px; - color: #f39c12; - font-weight: bold; - margin-bottom: 20px; - """) - status_label.setAlignment(Qt.AlignCenter) - layout.addWidget(status_label) - - # Description - desc_label = QLabel(description) - desc_label.setStyleSheet(""" - font-size: 12px; - color: #666; - line-height: 1.6; - """) - desc_label.setAlignment(Qt.AlignCenter) - desc_label.setWordWrap(True) - desc_label.setMaximumWidth(600) - layout.addWidget(desc_label) - - layout.addStretch() - - return widget - - def update_footpath_value(self, footpath_value): - """Update footpath value across all tabs""" - self.footpath_value = footpath_value - self.bridge_geometry_tab.update_footpath_value(footpath_value) diff --git a/src/osdagbridge/desktop/main.py b/src/osdagbridge/desktop/main.py deleted file mode 100644 index 418a2a6f..00000000 --- a/src/osdagbridge/desktop/main.py +++ /dev/null @@ -1,3 +0,0 @@ -"""Desktop bootstrap (PySide6 stub).""" -def start(): - print("Starting desktop UI (stub)") diff --git a/src/osdagbridge/desktop/legacy_src/src/osbridge/resources/resources.qrc b/src/osdagbridge/desktop/resources/resources.qrc similarity index 59% rename from src/osdagbridge/desktop/legacy_src/src/osbridge/resources/resources.qrc rename to src/osdagbridge/desktop/resources/resources.qrc index 9c4f7bb3..c847e516 100644 --- a/src/osdagbridge/desktop/legacy_src/src/osbridge/resources/resources.qrc +++ b/src/osdagbridge/desktop/resources/resources.qrc @@ -1,12 +1,21 @@ + + + + + + vectors/arrow_down_light.svg vectors/arrow_down_dark.svg vectors/arrow_up_light.svg vectors/arrow_up_dark.svg + themes/lightstyle.qss + vectors/design.svg vectors/save.svg + vectors/checked.svg vectors/design_report.svg vectors/lock_open.svg vectors/lock_close.svg diff --git a/src/osdagbridge/desktop/resources/resources_rc.py b/src/osdagbridge/desktop/resources/resources_rc.py new file mode 100644 index 00000000..ed54a87e --- /dev/null +++ b/src/osdagbridge/desktop/resources/resources_rc.py @@ -0,0 +1,651 @@ +# Resource object code (Python 3) +# Created by: object code +# Created by: The Resource Compiler for Qt version 6.9.2 +# WARNING! All changes made in this file will be lost! + +from PySide6 import QtCore + +qt_resource_data = b"\ +\x00\x00\x02\xec\ +\x00\ +\x00\x0a\x95x\xda\xb5V]O\xdb0\x14}\xaf\xd4\xff\ +p\x05/\x0c)\xf4\x0b\x0a\x0b\xdaC[JW\xa9\x1b\ +\xebR\x98\xd8\x0br\x127\xb1p\xec\xccq(\x0c\xf1\ +\xdf\xe7\x848M\xdb,\xb4hs^\xd2\x1b\xfb\x9es\ +\xef=9i\xe3\x10>\xed\xb4\xea5\x00\xb8\xb2.z\ +#\x18]\x8f\xc1\x9a\xddN\x86\xd6\xe7\xe1p\x06\x06\x5c\ +}\x1f\xf5\xbe\x8e\x7f\x0e/\xa0\x7f\x0b\xd6\xb7\xe1`|\ +9\x1e\x8cg\xb7\xe9\x99\xdd`\xe0\xb0Q\xaf\xd5k\x8d\ +w\xd1k\x1d\xc1hr\xd5\xefM2zp0\xc1(\ +\x92`\x85\xd8!s\xe2|x/\xa1CxN\x8f\xc2\ +\x9c3i\xccQ@\xe8\x93\x09{\xd7v\xccd\x0c\x16\ +b\xd1\xdey\xbd\xf6R\xafM\xbf \xc2~\x10\xe6\xf2\ +\x85>b#\xe7\xde\x13v\xee\ +m\xfe\x08\x96|R\x09\x92QN\xd3X_\xc5\x9e7\ +\xd9/I\xea\xcc\x9d\x8e.>\x0a\x91\x93\x16\xdf\xd5]\ +\xcfS\x99\xa6\x9a8q\x90\xe4Bg]\x10W\xfa*\ +c7\xcf\xe8c\xe2\xf9r%T\xd2\xefN\xba\xca[\ +\xd8Y\x1e,\xd1T\xba\xfeN\xcc\xf4\xf9\x03\xce\xe9\x95\ + \x9f\xa4\xab\x22\x81\x93\xc4\xb0[\xa5k\xcda\x9b\xe2\ +H\x80<\xd5\xf3X\xd0\x03\xb3\xf1\x80\x1d\x05\x1152\ +\x8c\xa3\xe8\xc1\xfb\xb09\xce\xe9\x80\x076\xef\xaf\xcdS\ +\xc7\x9e\xd7D\x9a\x00\x9fV\xf4\xda\xa6\xaa\x82\xf2N\x9f\ +Ttz\xe1\x13\x89WUR\xcc\x14\x10f\xe8Q\xb7\ +\xcf\x96R\xc9X\x9a\xa6\xab\xd4o(\x7f`\x9ap\x14\ +\xdb\x8e\xd2\xa0\xe0\xd4\xe0\x82\xa4\xefzV\xc3\xf9\xc6\x86\ +\x90GD\x12\xae\xb6H\x1e\x82H`V+\xa0x.\ +\x0b\xc6P\xc4U\x90\x06\x12b\xe9L\xa5\x13Hw\xdc\ +%\x9b\xefh\x92^\x8f\xa2 \xeavsC\xd4\x85\xd0\ +\xab_\x19\xe2\xf5\xc1Y%\x13\x93\xb3-\xc8\xc4\xe1\x7f\ +\xa2\x02\xd3\x9e\x1dI\x81\x1c9\x968\xb8!\xb8\xc2\xb4\ +\x8bc\xaf\xd6\x12\x8f%%L\xd5\xc28\xc3o\x22\xaa\ +7L\xddi\xdc\x12AmG%\xc3*g\xb7y`\ +i\xcb%\xd6\xbe%\xe77\x1de\xe5\xdbQb\x17+\ +\xcf\xd7*\xdf\x8a@\x84\xa9\x12J\xa5%Ua\xbcE\ +{'\x0ek\xdd\xf87L\x8e\xed\xb3V\xb7\xc4\x07'\ +J_C\x97\xc8\xa2\x0f\xe6\xb1]}p\xbfy\x9a\x5c\ +\xe5\xfa\xe8\xee\xe6\x84\xab\xff'\xd2O\xeb\x22{/\x19\ +\x17\x01\xa2\x95.\xf9\x07s\xef\x15`\ +\x00\x00\x00\xb2\ +<\ +svg xmlns=\x22http:\ +//www.w3.org/200\ +0/svg\x22 height=\x222\ +4px\x22 viewBox=\x220 \ +-960 960 960\x22 wi\ +dth=\x2224px\x22 fill=\ +\x22#90af13\x22>\ +\x00\x00\x02\xfe\ +<\ +svg xmlns=\x22http:\ +//www.w3.org/200\ +0/svg\x22 height=\x224\ +0px\x22 viewBox=\x220 \ +-960 960 960\x22 wi\ +dth=\x2240px\x22 fill=\ +\x22#000000\x22>\ +\x00\x00\x021\ +<\ +svg xmlns=\x22http:\ +//www.w3.org/200\ +0/svg\x22 height=\x224\ +0px\x22 viewBox=\x220 \ +-960 960 960\x22 wi\ +dth=\x2240px\x22 fill=\ +\x22#FFFFFF\x22>\ +\ +\x00\x00\x02\xd3\ +<\ +svg xmlns=\x22http:\ +//www.w3.org/200\ +0/svg\x22 height=\x224\ +0px\x22 viewBox=\x220 \ +-960 960 960\x22 wi\ +dth=\x2240px\x22 fill=\ +\x22#FFFFFF\x22>\ +\x00\x00\x05O\ +<\ +svg width=\x2240\x22 h\ +eight=\x2240\x22 viewB\ +ox=\x220 0 40 40\x22 f\ +ill=\x22none\x22 xmlns\ +=\x22http://www.w3.\ +org/2000/svg\x22>\x0d\x0a\ +\x0d\x0a\x0d\x0a\ +\x00\x00\x02\xde\ +<\ +svg xmlns=\x22http:\ +//www.w3.org/200\ +0/svg\x22 height=\x224\ +0px\x22 viewBox=\x220 \ +-960 960 960\x22 wi\ +dth=\x2240px\x22 fill=\ +\x22#000000\x22>\ +\x00\x00\x021\ +<\ +svg xmlns=\x22http:\ +//www.w3.org/200\ +0/svg\x22 height=\x224\ +0px\x22 viewBox=\x220 \ +-960 960 960\x22 wi\ +dth=\x2240px\x22 fill=\ +\x22#000000\x22>\ +\ +\x00\x00\x05O\ +<\ +svg width=\x2240\x22 h\ +eight=\x2240\x22 viewB\ +ox=\x220 0 40 40\x22 f\ +ill=\x22none\x22 xmlns\ +=\x22http://www.w3.\ +org/2000/svg\x22>\x0d\x0a\ +\x0d\x0a\x0d\x0a\ +\x00\x00\x02\xbb\ +<\ +svg xmlns=\x22http:\ +//www.w3.org/200\ +0/svg\x22 height=\x224\ +0px\x22 viewBox=\x220 \ +-960 960 960\x22 wi\ +dth=\x2240px\x22 fill=\ +\x22#FFFFFF\x22>\ +\x00\x00\x025\ +<\ +svg xmlns=\x22http:\ +//www.w3.org/200\ +0/svg\x22 height=\x224\ +0px\x22 viewBox=\x220 \ +-960 960 960\x22 wi\ +dth=\x2240px\x22 fill=\ +\x22#FFFFFF\x22>\ +" + +qt_resource_name = b"\ +\x00\x07\ +\x0c\xba\xb6s\ +\x00v\ +\x00e\x00c\x00t\x00o\x00r\x00s\ +\x00\x06\ +\x07\xae\xc3\xc3\ +\x00t\ +\x00h\x00e\x00m\x00e\x00s\ +\x00\x0e\ +\x03\x9b1c\ +\x00l\ +\x00i\x00g\x00h\x00t\x00s\x00t\x00y\x00l\x00e\x00.\x00q\x00s\x00s\ +\x00\x0b\ +\x01d\x8d\x87\ +\x00c\ +\x00h\x00e\x00c\x00k\x00e\x00d\x00.\x00s\x00v\x00g\ +\x00\x0e\ +\x0d\xd6\xeag\ +\x00l\ +\x00o\x00c\x00k\x00_\x00c\x00l\x00o\x00s\x00e\x00.\x00s\x00v\x00g\ +\x00\x13\ +\x0cPg\xa7\ +\x00a\ +\x00r\x00r\x00o\x00w\x00_\x00d\x00o\x00w\x00n\x00_\x00d\x00a\x00r\x00k\x00.\x00s\ +\x00v\x00g\ +\x00\x0a\ +\x0f\xec\x03\xe7\ +\x00d\ +\x00e\x00s\x00i\x00g\x00n\x00.\x00s\x00v\x00g\ +\x00\x12\ +\x0aDDG\ +\x00a\ +\x00r\x00r\x00o\x00w\x00_\x00u\x00p\x00_\x00l\x00i\x00g\x00h\x00t\x00.\x00s\x00v\ +\x00g\ +\x00\x0d\ +\x005w\xc7\ +\x00l\ +\x00o\x00c\x00k\x00_\x00o\x00p\x00e\x00n\x00.\x00s\x00v\x00g\ +\x00\x14\ +\x01\xd3}\xa7\ +\x00a\ +\x00r\x00r\x00o\x00w\x00_\x00d\x00o\x00w\x00n\x00_\x00l\x00i\x00g\x00h\x00t\x00.\ +\x00s\x00v\x00g\ +\x00\x11\ +\x03\xf9t'\ +\x00a\ +\x00r\x00r\x00o\x00w\x00_\x00u\x00p\x00_\x00d\x00a\x00r\x00k\x00.\x00s\x00v\x00g\ +\ +\x00\x11\ +\x08\xb1e\xc7\ +\x00d\ +\x00e\x00s\x00i\x00g\x00n\x00_\x00r\x00e\x00p\x00o\x00r\x00t\x00.\x00s\x00v\x00g\ +\ +\x00\x08\ +\x08\xc8U\xe7\ +\x00s\ +\x00a\x00v\x00e\x00.\x00s\x00v\x00g\ +" + +qt_resource_struct = b"\ +\x00\x00\x00\x00\x00\x02\x00\x00\x00\x02\x00\x00\x00\x01\ +\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x14\x00\x02\x00\x00\x00\x01\x00\x00\x00\x0d\ +\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x02\x00\x00\x00\x0a\x00\x00\x00\x03\ +\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\xf6\x00\x00\x00\x00\x00\x01\x00\x00\x11\x07\ +\x00\x00\x01\x9b\x03\xb6\xa6\x8f\ +\x00\x00\x00H\x00\x00\x00\x00\x00\x01\x00\x00\x02\xf0\ +\x00\x00\x01\x9b\x12,Gf\ +\x00\x00\x01\x16\x00\x00\x00\x00\x00\x01\x00\x00\x13\xe9\ +\x00\x00\x01\x9b\x03\xb6\xa6\x8b\ +\x00\x00\x01D\x00\x00\x00\x00\x00\x01\x00\x00\x16\x1e\ +\x00\x00\x01\x9b\x03\xb6\xa6\x8b\ +\x00\x00\x01l\x00\x00\x00\x00\x00\x01\x00\x00\x1bq\ +\x00\x00\x01\x9b\x03\xb6\xa6\x8d\ +\x00\x00\x01\x94\x00\x00\x00\x00\x00\x01\x00\x00\x1e0\ +\x00\x00\x01\x9b\x03\xb6\xa6\x8f\ +\x00\x00\x00\xcc\x00\x00\x00\x00\x00\x01\x00\x00\x0b\xb4\ +\x00\x00\x01\x9b\x03\xb6\xa6\x8c\ +\x00\x00\x00\x86\x00\x00\x00\x00\x00\x01\x00\x00\x06\xa8\ +\x00\x00\x01\x9b\x03\xb6\xa6\x89\ +\x00\x00\x00d\x00\x00\x00\x00\x00\x01\x00\x00\x03\xa6\ +\x00\x00\x01\x9b\x03\xb6\xa6\x8e\ +\x00\x00\x00\xb2\x00\x00\x00\x00\x00\x01\x00\x00\x08\xdd\ +\x00\x00\x01\x9b\x03\xb6\xa6\x8c\ +\x00\x00\x00&\x00\x01\x00\x00\x00\x01\x00\x00\x00\x00\ +\x00\x00\x01\x9b\x12>\x9d\xfb\ +" + +def qInitResources(): + QtCore.qRegisterResourceData(0x03, qt_resource_struct, qt_resource_name, qt_resource_data) + +def qCleanupResources(): + QtCore.qUnregisterResourceData(0x03, qt_resource_struct, qt_resource_name, qt_resource_data) + +qInitResources() diff --git a/src/osdagbridge/desktop/resources/themes/darkstyle.qss b/src/osdagbridge/desktop/resources/themes/darkstyle.qss new file mode 100644 index 00000000..f9c6f9ac --- /dev/null +++ b/src/osdagbridge/desktop/resources/themes/darkstyle.qss @@ -0,0 +1,32 @@ +/* ============================================== + OSDAG GUI STYLESHEET - ORGANIZED BY SPECIFICITY + ============================================== */ + +/* ============================================== + 1. GLOBAL STYLES (Least Specific) + ============================================== */ +* { + font-family: "Ubuntu Sans"; +} + +QMainWindow { + background-color: #282828; + border: 1px solid #6B7D20; + margin: 0px; + padding: 0px; +} + +QTabWidget { + background-color: #333333; + border: 0px; +} + +QToolTip { + background-color: #2B2B2B; + color: #D0D0D0; + border: 1px solid #6B7D20; + padding: 2px 2px; + font-size: 12px; + border-radius: 0px; + qproperty-alignment: AlignVCenter; +} \ No newline at end of file diff --git a/src/osdagbridge/desktop/resources/themes/lightstyle.qss b/src/osdagbridge/desktop/resources/themes/lightstyle.qss new file mode 100644 index 00000000..7c86c316 --- /dev/null +++ b/src/osdagbridge/desktop/resources/themes/lightstyle.qss @@ -0,0 +1,48 @@ +/* ============================================== + OSDAG GUI STYLESHEET - ORGANIZED BY SPECIFICITY + ============================================== */ + +/* ============================================== + 1. GLOBAL STYLES (Least Specific) + ============================================== */ +QMainWindow { + background-color: #f4f4f4; + border: 1px solid #90af13; + margin: 0px; + padding: 0px; +} + +QToolTip { + background-color: #FFFFFF; + color: #000000; + border: 1px solid #90AF13; + padding: 2px 2px; + font-size: 12px; + border-radius: 0px; + qproperty-alignment: AlignVCenter; +} +QSplitter::handle { + background-color: #D0D0D0; +} + +/* Global Checkbox Style */ +QCheckBox { + font-size: 10px; + color: #333; + spacing: 6px; +} +QCheckBox::indicator { + width: 16px; + height: 16px; + border: 1px solid #333333; + border-radius: 3px; + background-color: #ffffff; +} +QCheckBox::indicator:hover { + border: 1px solid #555555; +} +QCheckBox::indicator:checked { + background-color: #ffffff; + border: 1px solid #333333; + image: url(:/vectors/checked.svg); +} \ No newline at end of file diff --git a/src/osdagbridge/desktop/legacy_src/src/osbridge/resources/vectors/arrow_down_dark.svg b/src/osdagbridge/desktop/resources/vectors/arrow_down_dark.svg similarity index 100% rename from src/osdagbridge/desktop/legacy_src/src/osbridge/resources/vectors/arrow_down_dark.svg rename to src/osdagbridge/desktop/resources/vectors/arrow_down_dark.svg diff --git a/src/osdagbridge/desktop/legacy_src/src/osbridge/resources/vectors/arrow_down_light.svg b/src/osdagbridge/desktop/resources/vectors/arrow_down_light.svg similarity index 100% rename from src/osdagbridge/desktop/legacy_src/src/osbridge/resources/vectors/arrow_down_light.svg rename to src/osdagbridge/desktop/resources/vectors/arrow_down_light.svg diff --git a/src/osdagbridge/desktop/legacy_src/src/osbridge/resources/vectors/arrow_up_dark.svg b/src/osdagbridge/desktop/resources/vectors/arrow_up_dark.svg similarity index 100% rename from src/osdagbridge/desktop/legacy_src/src/osbridge/resources/vectors/arrow_up_dark.svg rename to src/osdagbridge/desktop/resources/vectors/arrow_up_dark.svg diff --git a/src/osdagbridge/desktop/legacy_src/src/osbridge/resources/vectors/arrow_up_light.svg b/src/osdagbridge/desktop/resources/vectors/arrow_up_light.svg similarity index 100% rename from src/osdagbridge/desktop/legacy_src/src/osbridge/resources/vectors/arrow_up_light.svg rename to src/osdagbridge/desktop/resources/vectors/arrow_up_light.svg diff --git a/src/osdagbridge/desktop/resources/vectors/checked.svg b/src/osdagbridge/desktop/resources/vectors/checked.svg new file mode 100644 index 00000000..829f9eda --- /dev/null +++ b/src/osdagbridge/desktop/resources/vectors/checked.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/osdagbridge/desktop/legacy_src/src/osbridge/resources/vectors/design.svg b/src/osdagbridge/desktop/resources/vectors/design.svg similarity index 100% rename from src/osdagbridge/desktop/legacy_src/src/osbridge/resources/vectors/design.svg rename to src/osdagbridge/desktop/resources/vectors/design.svg diff --git a/src/osdagbridge/desktop/legacy_src/src/osbridge/resources/vectors/design_report.svg b/src/osdagbridge/desktop/resources/vectors/design_report.svg similarity index 100% rename from src/osdagbridge/desktop/legacy_src/src/osbridge/resources/vectors/design_report.svg rename to src/osdagbridge/desktop/resources/vectors/design_report.svg diff --git a/src/osdagbridge/desktop/legacy_src/src/osbridge/resources/vectors/lock_close.svg b/src/osdagbridge/desktop/resources/vectors/lock_close.svg similarity index 100% rename from src/osdagbridge/desktop/legacy_src/src/osbridge/resources/vectors/lock_close.svg rename to src/osdagbridge/desktop/resources/vectors/lock_close.svg diff --git a/src/osdagbridge/desktop/legacy_src/src/osbridge/resources/vectors/lock_open.svg b/src/osdagbridge/desktop/resources/vectors/lock_open.svg similarity index 100% rename from src/osdagbridge/desktop/legacy_src/src/osbridge/resources/vectors/lock_open.svg rename to src/osdagbridge/desktop/resources/vectors/lock_open.svg diff --git a/src/osdagbridge/desktop/legacy_src/src/osbridge/resources/vectors/save.svg b/src/osdagbridge/desktop/resources/vectors/save.svg similarity index 100% rename from src/osdagbridge/desktop/legacy_src/src/osbridge/resources/vectors/save.svg rename to src/osdagbridge/desktop/resources/vectors/save.svg diff --git a/src/osdagbridge/desktop/ui/__init__.py b/src/osdagbridge/desktop/ui/__init__.py deleted file mode 100644 index 511085aa..00000000 --- a/src/osdagbridge/desktop/ui/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""UI package (Qt .ui files go here).""" diff --git a/src/osdagbridge/desktop/ui/additional_inputs.py b/src/osdagbridge/desktop/ui/additional_inputs.py new file mode 100644 index 00000000..f1503c81 --- /dev/null +++ b/src/osdagbridge/desktop/ui/additional_inputs.py @@ -0,0 +1,4017 @@ +""" +Additional Inputs Widget for Highway Bridge Design +Provides detailed input fields for manual bridge parameter definition +""" +import sys +import os +from PySide6.QtWidgets import ( + QWidget, QVBoxLayout, QHBoxLayout, QTabWidget, QTabBar, QLabel, QLineEdit, + QComboBox, QGroupBox, QFormLayout, QPushButton, QScrollArea, + QCheckBox, QMessageBox, QSizePolicy, QSpacerItem, QStackedWidget, + QFrame, QGridLayout, QTableWidget, QTableWidgetItem, QHeaderView, + QTextEdit, QDialog +) +from PySide6.QtCore import Qt, Signal, QSize +from PySide6.QtGui import QDoubleValidator, QIntValidator + +from osdagbridge.core.utils.common import * +import osdagbridge.desktop.resources.resources_rc + +def get_combobox_style(): + """Return the common stylesheet for dropdowns with the SVG icon from resources.""" + return """ + QComboBox { + padding: 2px 28px 2px 8px; + border: 1px solid #000000; + border-radius: 5px; + background-color: #ffffff; + color: #000000; + font-size: 12px; + min-height: 28px; + } + QComboBox:hover { + border: 1px solid #5d5d5d; + } + QComboBox:focus { + border: 1px solid #90AF13; + } + QComboBox::drop-down { + subcontrol-origin: padding; + subcontrol-position: top right; + width: 24px; + border: none; + margin-right: 4px; + } + QComboBox::down-arrow { + image: url(:/vectors/arrow_down_light.svg); + width: 16px; + height: 16px; + } + QComboBox::down-arrow:on { + image: url(:/vectors/arrow_up_light.svg); + width: 16px; + height: 16px; + } + QComboBox QAbstractItemView { + background-color: #ffffff; + border: 1px solid #000000; + outline: none; + } + QComboBox QAbstractItemView::item { + color: #000000; + padding: 4px 8px; + } + QComboBox QAbstractItemView::item:selected { + background-color: #90AF13; + color: #000000; + } + """ + + +def get_lineedit_style(): + """Return the shared stylesheet for line edits in the section inputs.""" + return """ + QLineEdit { + padding: 1px 7px; + border: 1px solid #070707; + border-radius: 6px; + background-color: #ffffff; + color: #000000; + font-size: 12px; + min-height: 28px; + } + QLineEdit:hover { + border: 1px solid #5d5d5d; + } + QLineEdit:focus { + border: 1px solid #90AF13; + } + QLineEdit:disabled { + background-color: #f5f5f5; + color: #9b9b9b; + } + """ + + +def apply_field_style(widget): + """Apply the appropriate style to combo boxes and line edits.""" + widget.setMinimumHeight(28) + if isinstance(widget, QComboBox): + widget.setStyleSheet(get_combobox_style()) + elif isinstance(widget, QLineEdit): + widget.setStyleSheet(get_lineedit_style()) + + +def create_action_button_bar(parent=None): + """Create a standardized Defaults/Save bar with gray backing.""" + frame = QFrame(parent) + frame.setObjectName("actionButtonBar") + frame.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) + frame.setStyleSheet( + "QFrame#actionButtonBar {" + " background-color: #ededed;" + " border: 1px solid #c8c8c8;" + " border-radius: 6px;" + "}" + "QFrame#actionButtonBar QPushButton {" + " background-color: #ffffff;" + " color: #2f2f2f;" + " font-weight: 600;" + " border: 1px solid #8c8c8c;" + " border-radius: 4px;" + " padding: 6px 24px;" + " min-width: 120px;" + "}" + "QFrame#actionButtonBar QPushButton:hover {" + " background-color: #f6f6f6;" + "}" + "QFrame#actionButtonBar QPushButton:pressed {" + " background-color: #e0e0e0;" + "}" + ) + + layout = QHBoxLayout(frame) + layout.setContentsMargins(22, 10, 22, 10) + layout.setSpacing(12) + layout.addStretch() + + defaults_button = QPushButton("Defaults", frame) + save_button = QPushButton("Save", frame) + layout.addWidget(defaults_button) + layout.addWidget(save_button) + layout.addStretch() + + return frame, defaults_button, save_button + + +SECTION_NAV_BUTTON_STYLE = """ + QPushButton { + background-color: #f4f4f4; + border: 2px solid #d2d2d2; + border-radius: 12px; + padding: 20px 16px; + text-align: left; + font-weight: bold; + font-size: 12px; + color: #333333; + } + QPushButton:hover { + border-color: #b5b5b5; + } + QPushButton:checked { + background-color: #9ecb3d; + border-color: #7da523; + color: #ffffff; + } +""" + + +class OptimizableField(QWidget): + """Widget that allows selection between Optimized/Customized/All modes with input field""" + + def __init__(self, label_text, parent=None): + super().__init__(parent) + self.layout = QHBoxLayout(self) + self.layout.setContentsMargins(0, 0, 0, 0) + self.layout.setSpacing(8) + + self.mode_combo = QComboBox() + self.mode_combo.addItems(VALUES_OPTIMIZATION_MODE) + self.mode_combo.setMinimumWidth(140) + self.mode_combo.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) + + self.input_field = QLineEdit() + self.input_field.setEnabled(False) + self.input_field.setVisible(False) + self.input_field.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) + + self.layout.addWidget(self.mode_combo) + self.layout.addWidget(self.input_field) + + self.mode_combo.currentTextChanged.connect(self.on_mode_changed) + self.on_mode_changed(self.mode_combo.currentText()) + + def on_mode_changed(self, text): + """Enable/disable input field based on selection""" + if text in ("Optimized", "All", "NA"): + self.input_field.setEnabled(False) + self.input_field.clear() + self.input_field.setVisible(False) + else: + self.input_field.setEnabled(True) + self.input_field.setVisible(True) + + def get_value(self): + """Returns tuple of (mode, value)""" + return (self.mode_combo.currentText(), self.input_field.text()) + + +class TypicalSectionDetailsTab(QWidget): + """Sub-tab for Typical Section Details inputs""" + + footpath_changed = Signal(str) + + def __init__(self, footpath_value="None", carriageway_width=7.5, parent=None): + super().__init__(parent) + self.footpath_value = footpath_value + self.carriageway_width = carriageway_width + self.updating_fields = False + self.init_ui() + + def style_input_field(self, field): + apply_field_style(field) + + def style_group_box(self, group_box): + group_box.setStyleSheet(""" + QGroupBox { + font-weight: bold; + border: 2px solid #d0d0d0; + border-radius: 6px; + margin-top: 12px; + padding-top: 15px; + background-color: #f9f9f9; + } + QGroupBox::title { + subcontrol-origin: margin; + subcontrol-position: top left; + left: 10px; + padding: 0 5px; + background-color: white; + color: #4a7ba7; + } + """) + + def _create_section_card(self, title): + card = QFrame() + card.setObjectName("sectionCard") + card.setStyleSheet(""" + QFrame#sectionCard { + background-color: #f5f5f5; + border: none; + } + """) + card_layout = QVBoxLayout(card) + card_layout.setContentsMargins(0, 0, 0, 0) + card_layout.setSpacing(12) + + title_label = QLabel(title) + title_label.setStyleSheet("font-size: 12px; font-weight: bold; color: #000;") + card_layout.addWidget(title_label) + + return card, card_layout + + def init_ui(self): + main_layout = QVBoxLayout(self) + main_layout.setContentsMargins(10, 10, 10, 10) + main_layout.setSpacing(0) + + diagram_widget = QWidget() + diagram_widget.setStyleSheet(""" + QWidget { + background-color: #d9d9d9; + border: 1px solid #b0b0b0; + border-radius: 8px; + } + """) + diagram_widget.setMinimumHeight(150) + diagram_widget.setMaximumHeight(200) + diagram_layout = QVBoxLayout(diagram_widget) + diagram_layout.setContentsMargins(20, 20, 20, 20) + diagram_layout.setAlignment(Qt.AlignCenter) + + diagram_label = QLabel("Typical Section Details\nDiagram") + diagram_label.setAlignment(Qt.AlignCenter) + diagram_label.setStyleSheet(""" + QLabel { + background-color: transparent; + border: none; + padding: 20px; + font-size: 13px; + color: #333; + } + """) + diagram_layout.addWidget(diagram_label) + + main_layout.addWidget(diagram_widget) + main_layout.addSpacing(10) + + input_container = QWidget() + input_container.setStyleSheet("QWidget { background-color: white; }") + input_layout = QVBoxLayout(input_container) + input_layout.setContentsMargins(0, 0, 0, 0) + input_layout.setSpacing(0) + + self.input_tabs = QTabWidget() + self.input_tabs.setStyleSheet(""" + QTabWidget::pane { + border: 1px solid #b0b0b0; + border-top: none; + background-color: #f5f5f5; + border-radius: 0px 0px 8px 8px; + } + QTabBar::tab { + background-color: #e8e8e8; + color: #555; + padding: 10px 20px; + border: 1px solid #b0b0b0; + border-bottom: none; + border-right: none; + font-size: 11px; + min-width: 80px; + } + QTabBar::tab:last { + border-right: 1px solid #b0b0b0; + } + QTabBar::tab:selected { + background-color: #90AF13; + color: white; + font-weight: bold; + border: 1px solid #90AF13; + border-bottom: none; + } + QTabBar::tab:hover:!selected { + background-color: #d0d0d0; + } + """) + + self.create_layout_tab() + self.create_crash_barrier_tab() + self.create_median_tab() + self.create_railing_tab() + self.create_wearing_course_tab() + self.create_lane_details_tab() + + input_layout.addWidget(self.input_tabs) + main_layout.addWidget(input_container) + + action_bar, self.defaults_button, self.save_button = create_action_button_bar() + self.defaults_button.clicked.connect(lambda: self._show_placeholder_message("Defaults")) + self.save_button.clicked.connect(lambda: self._show_placeholder_message("Save")) + main_layout.addSpacing(8) + main_layout.addWidget(action_bar) + + self.deck_thickness.textChanged.connect(self.update_footpath_thickness) + self.recalculate_girders() + + def create_layout_tab(self): + layout_widget = QWidget() + layout_widget.setStyleSheet("background-color: #f5f5f5;") + layout_layout = QVBoxLayout(layout_widget) + layout_layout.setContentsMargins(18, 6, 18, 12) + layout_layout.setSpacing(0) + + title_label = QLabel("Inputs:") + title_label.setStyleSheet("font-size: 12px; font-weight: bold; color: #000;") + layout_layout.addWidget(title_label) + layout_layout.addSpacing(8) + + grid = QGridLayout() + grid.setHorizontalSpacing(24) + grid.setVerticalSpacing(10) + grid.setColumnStretch(1, 1) + grid.setColumnStretch(3, 1) + grid.setContentsMargins(0, 0, 0, 0) + + def _label(text): + lbl = QLabel(text) + lbl.setStyleSheet("font-size: 11px; color: #000;") + lbl.setMinimumWidth(180) + return lbl + + self.girder_spacing = QLineEdit() + self.girder_spacing.setValidator(QDoubleValidator(0.01, 50.0, 3)) + self.girder_spacing.setText(str(DEFAULT_GIRDER_SPACING)) + self.style_input_field(self.girder_spacing) + self.girder_spacing.textChanged.connect(self.on_girder_spacing_changed) + + self.no_of_girders = QLineEdit() + self.no_of_girders.setValidator(QIntValidator(2, 100)) + self.style_input_field(self.no_of_girders) + self.no_of_girders.textChanged.connect(self.on_no_of_girders_changed) + + grid.addWidget(_label("Girder Spacing (m):"), 0, 0, Qt.AlignLeft) + grid.addWidget(self.girder_spacing, 0, 1) + grid.addWidget(_label("No. of Girders:"), 0, 2, Qt.AlignLeft) + grid.addWidget(self.no_of_girders, 0, 3) + + self.deck_overhang = QLineEdit() + self.deck_overhang.setValidator(QDoubleValidator(0.0, 10.0, 3)) + self.deck_overhang.setText(str(DEFAULT_DECK_OVERHANG)) + self.style_input_field(self.deck_overhang) + self.deck_overhang.textChanged.connect(self.on_deck_overhang_changed) + + values_adjusted_label = QLabel("Values adjusted for:") + values_adjusted_label.setStyleSheet("font-size: 11px; color: #5b5b5b; font-style: italic;") + + grid.addWidget(_label("Deck Overhang Width (m):"), 1, 0, Qt.AlignLeft) + grid.addWidget(self.deck_overhang, 1, 1) + #grid.addWidget(values_adjusted_label, 1, 2, 1, 2, Qt.AlignLeft) + + self.overall_bridge_width_display = QLineEdit() + self.style_input_field(self.overall_bridge_width_display) + self.overall_bridge_width_display.setReadOnly(True) + self.overall_bridge_width_display.setEnabled(False) + + grid.addWidget(_label("Overall Bridge Width (m):"), 2, 0, Qt.AlignLeft) + grid.addWidget(self.overall_bridge_width_display, 2, 1) + + self.deck_thickness = QLineEdit() + self.deck_thickness.setValidator(QDoubleValidator(0.0, 500.0, 0)) + self.style_input_field(self.deck_thickness) + + self.footpath_thickness = QLineEdit() + self.footpath_thickness.setValidator(QDoubleValidator(0.0, 500.0, 0)) + self.style_input_field(self.footpath_thickness) + + grid.addWidget(_label("Deck Thickness (mm):"), 3, 0, Qt.AlignLeft) + grid.addWidget(self.deck_thickness, 3, 1) + grid.addWidget(_label("Footpath Thickness (mm):"), 4, 2, Qt.AlignLeft) + grid.addWidget(self.footpath_thickness, 4, 3) + + self.footpath_width = QLineEdit() + self.footpath_width.setValidator(QDoubleValidator(MIN_FOOTPATH_WIDTH, 5.0, 3)) + self.footpath_width.textChanged.connect(self.on_footpath_width_changed) + self.style_input_field(self.footpath_width) + self.footpath_width.setText(f"{MIN_FOOTPATH_WIDTH:.2f}") + + grid.addWidget(_label("Footpath Width (m):"), 4, 0, Qt.AlignLeft) + grid.addWidget(self.footpath_width, 4, 1) + + layout_layout.addLayout(grid) + # CHANGED: Add stretch at bottom to push content up + layout_layout.addStretch() + + self.input_tabs.addTab(layout_widget, "Layout") + def create_crash_barrier_tab(self): + crash_widget = QWidget() + crash_widget.setStyleSheet("background-color: #f5f5f5;") + crash_layout = QVBoxLayout(crash_widget) + crash_layout.setContentsMargins(18, 6, 18, 12) + crash_layout.setSpacing(0) + + card, card_layout = self._create_section_card("Crash Barrier Inputs:") + grid = QGridLayout() + grid.setContentsMargins(0, 0, 0, 0) + grid.setHorizontalSpacing(24) + grid.setVerticalSpacing(10) + grid.setColumnStretch(1, 1) + + def add_row(row, label_text, widget): + label = QLabel(label_text) + label.setStyleSheet("font-size: 11px; color: #000;") + label.setMinimumWidth(210) + grid.addWidget(label, row, 0, Qt.AlignLeft) + grid.addWidget(widget, row, 1) + + self.crash_barrier_type = QComboBox() + self.crash_barrier_type.addItems(VALUES_CRASH_BARRIER_TYPE) + self.style_input_field(self.crash_barrier_type) + self.crash_barrier_type.currentTextChanged.connect(self.on_crash_barrier_type_changed) + add_row(0, "Type:", self.crash_barrier_type) + + self.crash_barrier_density = QLineEdit() + self.crash_barrier_density.setValidator(QDoubleValidator(0.0, 100.0, 2)) + self.style_input_field(self.crash_barrier_density) + add_row(1, "Material Density (kN/m^3):", self.crash_barrier_density) + + self.crash_barrier_width = QLineEdit() + self.crash_barrier_width.setValidator(QDoubleValidator(0.0, 2.0, 3)) + self.crash_barrier_width.setText(str(DEFAULT_CRASH_BARRIER_WIDTH)) + self.style_input_field(self.crash_barrier_width) + self.crash_barrier_width.textChanged.connect(self.recalculate_girders) + add_row(2, "Width (m):", self.crash_barrier_width) + + self.crash_barrier_height = QLineEdit() + self.crash_barrier_height.setValidator(QDoubleValidator(0.0, 3.0, 3)) + self.style_input_field(self.crash_barrier_height) + add_row(3, "Height (m):", self.crash_barrier_height) + + self.crash_barrier_area = QLineEdit() + self.crash_barrier_area.setValidator(QDoubleValidator(0.0, 10.0, 4)) + self.style_input_field(self.crash_barrier_area) + add_row(4, "Area (m^2):", self.crash_barrier_area) + + card_layout.addLayout(grid) + crash_layout.addWidget(card) + crash_layout.addStretch() + self.input_tabs.addTab(crash_widget, "Crash Barrier") + + def create_median_tab(self): + median_widget = QWidget() + median_widget.setStyleSheet("background-color: #f5f5f5;") + median_layout = QVBoxLayout(median_widget) + median_layout.setContentsMargins(18, 6, 18, 12) + median_layout.setSpacing(0) + + card, card_layout = self._create_section_card("Median Inputs:") + grid = QGridLayout() + grid.setContentsMargins(0, 0, 0, 0) + grid.setHorizontalSpacing(24) + grid.setVerticalSpacing(10) + grid.setColumnStretch(1, 1) + + def add_row(row, label_text, widget): + label = QLabel(label_text) + label.setStyleSheet("font-size: 11px; color: #000;") + label.setMinimumWidth(210) + grid.addWidget(label, row, 0, Qt.AlignLeft) + grid.addWidget(widget, row, 1) + + self.median_type = QComboBox() + self.median_type.addItems(VALUES_MEDIAN_TYPE) + self.style_input_field(self.median_type) + add_row(0, "Type:", self.median_type) + + self.median_density = QLineEdit() + self.median_density.setValidator(QDoubleValidator(0.0, 100.0, 2)) + self.style_input_field(self.median_density) + add_row(1, "Material Density (kN/m^3):", self.median_density) + + self.median_width = QLineEdit() + self.median_width.setValidator(QDoubleValidator(0.0, 3.0, 3)) + self.style_input_field(self.median_width) + add_row(2, "Width (m):", self.median_width) + + self.median_height = QLineEdit() + self.median_height.setValidator(QDoubleValidator(0.0, 3.0, 3)) + self.style_input_field(self.median_height) + add_row(3, "Height (m):", self.median_height) + + self.median_area = QLineEdit() + self.median_area.setValidator(QDoubleValidator(0.0, 10.0, 4)) + self.style_input_field(self.median_area) + add_row(4, "Area (m^2):", self.median_area) + + card_layout.addLayout(grid) + median_layout.addWidget(card) + median_layout.addStretch() + self.input_tabs.addTab(median_widget, "Median") + + def create_railing_tab(self): + railing_widget = QWidget() + railing_widget.setStyleSheet("background-color: #f5f5f5;") + railing_layout = QVBoxLayout(railing_widget) + railing_layout.setContentsMargins(18, 6, 18, 12) + railing_layout.setSpacing(0) + + card, card_layout = self._create_section_card("Railing Inputs:") + grid = QGridLayout() + grid.setContentsMargins(0, 0, 0, 0) + grid.setHorizontalSpacing(24) + grid.setVerticalSpacing(10) + grid.setColumnStretch(1, 1) + + def add_row(row, label_text, widget): + label = QLabel(label_text) + label.setStyleSheet("font-size: 11px; color: #000;") + label.setMinimumWidth(180) + grid.addWidget(label, row, 0, Qt.AlignLeft) + grid.addWidget(widget, row, 1) + + self.railing_type = QComboBox() + self.railing_type.addItems(VALUES_RAILING_TYPE) + self.style_input_field(self.railing_type) + add_row(0, "Type:", self.railing_type) + + self.railing_width = QLineEdit() + self.railing_width.setValidator(QDoubleValidator(0.0, 2000.0, 1)) + self.railing_width.setText(f"{DEFAULT_RAILING_WIDTH * 1000:.0f}") + self.style_input_field(self.railing_width) + self.railing_width.textChanged.connect(self.recalculate_girders) + add_row(1, "Width (mm):", self.railing_width) + + self.railing_height = QLineEdit() + self.railing_height.setValidator(QDoubleValidator(MIN_RAILING_HEIGHT, 3.0, 3)) + self.style_input_field(self.railing_height) + self.railing_height.editingFinished.connect(self.validate_railing_height) + add_row(2, "Height (m):", self.railing_height) + + load_row = QHBoxLayout() + load_row.setContentsMargins(0, 0, 0, 0) + load_row.setSpacing(12) + + self.railing_load_mode = QComboBox() + self.railing_load_mode.addItems(["Automatic (IRC 6)", "User-defined"]) + self.style_input_field(self.railing_load_mode) + self.railing_load_mode.currentTextChanged.connect(self.on_railing_load_mode_changed) + load_row.addWidget(self.railing_load_mode) + + self.railing_load_value = QLineEdit() + self.railing_load_value.setValidator(QDoubleValidator(0.0, 50.0, 2)) + self.railing_load_value.setPlaceholderText("Value") + self.railing_load_value.setEnabled(False) + self.style_input_field(self.railing_load_value) + load_row.addWidget(self.railing_load_value) + + load_container = QWidget() + load_container.setLayout(load_row) + add_row(3, "Load (kN/m):", load_container) + + card_layout.addLayout(grid) + railing_layout.addWidget(card) + railing_layout.addStretch() + self.input_tabs.addTab(railing_widget, "Railing") + + def create_wearing_course_tab(self): + wearing_widget = QWidget() + wearing_widget.setStyleSheet("background-color: #f5f5f5;") + wearing_layout = QVBoxLayout(wearing_widget) + wearing_layout.setContentsMargins(18, 6, 18, 12) + wearing_layout.setSpacing(0) + + card, card_layout = self._create_section_card("Wearing Course Inputs:") + grid = QGridLayout() + grid.setContentsMargins(0, 0, 0, 0) + grid.setHorizontalSpacing(24) + grid.setVerticalSpacing(10) + grid.setColumnStretch(1, 1) + + def add_row(row, label_text, widget): + label = QLabel(label_text) + label.setStyleSheet("font-size: 11px; color: #000;") + label.setMinimumWidth(200) + grid.addWidget(label, row, 0, Qt.AlignLeft) + grid.addWidget(widget, row, 1) + + self.wearing_material = QComboBox() + self.wearing_material.addItems(VALUES_WEARING_COAT_MATERIAL) + self.style_input_field(self.wearing_material) + add_row(0, "Material:", self.wearing_material) + + self.wearing_density = QLineEdit() + self.wearing_density.setValidator(QDoubleValidator(0.0, 40.0, 2)) + self.style_input_field(self.wearing_density) + add_row(1, "Density (kN/m^3):", self.wearing_density) + + self.wearing_thickness = QLineEdit() + self.wearing_thickness.setValidator(QDoubleValidator(0.0, 200.0, 1)) + self.style_input_field(self.wearing_thickness) + add_row(2, "Thickness (mm):", self.wearing_thickness) + + card_layout.addLayout(grid) + wearing_layout.addWidget(card) + wearing_layout.addStretch() + self.input_tabs.addTab(wearing_widget, "Wearing Course") + + def create_lane_details_tab(self): + lane_widget = QWidget() + lane_widget.setStyleSheet("background-color: #f5f5f5;") + lane_layout = QVBoxLayout(lane_widget) + lane_layout.setContentsMargins(18, 6, 18, 12) + lane_layout.setSpacing(0) + + card, card_layout = self._create_section_card("Inputs:") + + selector_layout = QHBoxLayout() + selector_layout.setContentsMargins(0, 0, 0, 0) + selector_layout.setSpacing(12) + + lanes_label = QLabel("No. of Traffic Lanes:") + lanes_label.setStyleSheet("font-size: 11px; color: #000;") + selector_layout.addWidget(lanes_label) + + self.lane_count_combo = QComboBox() + self.lane_count_combo.addItems([str(i) for i in range(1, 7)]) + self.style_input_field(self.lane_count_combo) + self.lane_count_combo.currentTextChanged.connect(self.on_lane_count_changed) + selector_layout.addWidget(self.lane_count_combo) + selector_layout.addStretch() + + card_layout.addLayout(selector_layout) + + self.lane_table = QTableWidget() + self.lane_table.setColumnCount(3) + self.lane_table.setHorizontalHeaderLabels([ + "Traffic Lane Number", + "Distance from inner edge of crash barrier to left edge of lane (m)", + "Lane Width (m)" + ]) + header = self.lane_table.horizontalHeader() + header.setSectionResizeMode(0, QHeaderView.ResizeToContents) + header.setSectionResizeMode(1, QHeaderView.Stretch) + header.setSectionResizeMode(2, QHeaderView.ResizeToContents) + self.lane_table.verticalHeader().setVisible(False) + self.lane_table.setAlternatingRowColors(True) + self.lane_table.setStyleSheet(""" + QTableWidget { + background-color: #ffffff; + alternate-background-color: #f9f9f9; + gridline-color: #e0e0e0; + border: 1px solid #e0e0e0; + } + QTableWidget::item { + padding: 8px; + border-bottom: 1px solid #e0e0e0; + } + QTableWidget::item:hover { + background-color: #e8f4f8; + } + QHeaderView::section { + background-color: #f5f5f5; + color: #333; + padding: 8px; + border: 1px solid #e0e0e0; + font-weight: bold; + font-size: 11px; + } + """) + + card_layout.addWidget(self.lane_table) + lane_layout.addWidget(card) + lane_layout.addStretch() + + self.input_tabs.addTab(lane_widget, "Lane Details") + self._update_lane_details_rows(self.lane_count_combo.currentText()) + + def _update_lane_details_rows(self, count): + try: + num_lanes = int(count) + self.lane_table.setRowCount(num_lanes) + + for i in range(num_lanes): + # Lane number (non-editable) + lane_num_item = QTableWidgetItem(str(i + 1)) + lane_num_item.setFlags(lane_num_item.flags() & ~Qt.ItemIsEditable) + lane_num_item.setTextAlignment(Qt.AlignCenter) + self.lane_table.setItem(i, 0, lane_num_item) + + # Distance field (editable) + if not self.lane_table.item(i, 1): + self.lane_table.setItem(i, 1, QTableWidgetItem("")) + + # Width field (editable) + if not self.lane_table.item(i, 2): + self.lane_table.setItem(i, 2, QTableWidgetItem("")) + except ValueError: + pass + + def update_footpath_value(self, footpath_value): + self.footpath_value = footpath_value + if hasattr(self, "footpath_width"): + self.footpath_width.setEnabled(footpath_value != "None") + self.footpath_thickness.setEnabled(footpath_value != "None") + self.recalculate_girders() + self.footpath_changed.emit(footpath_value) + + def get_overall_bridge_width(self): + try: + overall_width = self.carriageway_width + if self.footpath_value != "None": + footpath_width = float(self.footpath_width.text()) if self.footpath_width.text() else 0 + num_footpaths = 2 if self.footpath_value == "Both" else (1 if self.footpath_value == "Single Sided" else 0) + overall_width += footpath_width * num_footpaths + + crash_barrier_width = float(self.crash_barrier_width.text()) if self.crash_barrier_width.text() else DEFAULT_CRASH_BARRIER_WIDTH + overall_width += crash_barrier_width * 2 + + if self.footpath_value != "None": + railing_width_text = self.railing_width.text() if hasattr(self, "railing_width") else "" + if railing_width_text: + railing_width = float(railing_width_text) / 1000.0 + else: + railing_width = DEFAULT_RAILING_WIDTH + overall_width += railing_width * 2 + + return overall_width + except: + return self.carriageway_width + + def _update_overall_bridge_width_display(self): + if hasattr(self, "overall_bridge_width_display"): + try: + overall_width = self.get_overall_bridge_width() + self.overall_bridge_width_display.setText(f"{overall_width:.3f}") + except: + self.overall_bridge_width_display.clear() + + def recalculate_girders(self): + if self.updating_fields: + return + try: + self._update_overall_bridge_width_display() + overall_width = self.get_overall_bridge_width() + spacing = float(self.girder_spacing.text()) if self.girder_spacing.text() else DEFAULT_GIRDER_SPACING + overhang = float(self.deck_overhang.text()) if self.deck_overhang.text() else DEFAULT_DECK_OVERHANG + if spacing >= overall_width or overhang >= overall_width: + self.no_of_girders.setText("") + return + if spacing > 0: + no_girders = int(round((overall_width - 2 * overhang) / spacing)) + 1 + if no_girders >= 2: + self.updating_fields = True + self.no_of_girders.setText(str(no_girders)) + self.updating_fields = False + except: + pass + + def on_girder_spacing_changed(self): + if not self.updating_fields: + try: + overall_width = self.get_overall_bridge_width() + spacing_text = self.girder_spacing.text() + if spacing_text: + spacing = float(spacing_text) + if spacing >= overall_width: + QMessageBox.warning(self, "Invalid Girder Spacing", + f"Girder spacing ({spacing:.2f} m) must be less than overall bridge width ({overall_width:.2f} m).") + return + self.recalculate_girders() + except: + pass + + def on_deck_overhang_changed(self): + if not self.updating_fields: + try: + overall_width = self.get_overall_bridge_width() + overhang_text = self.deck_overhang.text() + if overhang_text: + overhang = float(overhang_text) + if overhang >= overall_width: + QMessageBox.warning(self, "Invalid Deck Overhang", + f"Deck overhang ({overhang:.2f} m) must be less than overall bridge width ({overall_width:.2f} m).") + return + self.recalculate_girders() + except: + pass + + def on_no_of_girders_changed(self): + if not self.updating_fields: + try: + no_girders_text = self.no_of_girders.text() + if no_girders_text: + no_girders = int(no_girders_text) + if no_girders < 2: + QMessageBox.warning(self, "Invalid Number of Girders", + "Number of girders must be at least 2.") + return + overall_width = self.get_overall_bridge_width() + overhang = float(self.deck_overhang.text()) if self.deck_overhang.text() else DEFAULT_DECK_OVERHANG + if no_girders > 1: + new_spacing = (overall_width - 2 * overhang) / (no_girders - 1) + self.updating_fields = True + self.girder_spacing.setText(f"{new_spacing:.3f}") + self.updating_fields = False + except: + pass + + def on_footpath_width_changed(self): + if not self.updating_fields: + self.recalculate_girders() + + def validate_footpath_width(self): + try: + if self.footpath_width.text(): + width = float(self.footpath_width.text()) + if width < MIN_FOOTPATH_WIDTH: + QMessageBox.critical(self, "Footpath Width Error", + f"Footpath width must be at least {MIN_FOOTPATH_WIDTH} m as per IRC 5 Clause 104.3.6.") + except: + pass + + def validate_railing_height(self): + try: + if self.railing_height.text(): + height = float(self.railing_height.text()) + if height < MIN_RAILING_HEIGHT: + QMessageBox.critical(self, "Railing Height Error", + f"Railing height must be at least {MIN_RAILING_HEIGHT} m as per IRC 5 Clauses 109.7.2.3 and 109.7.2.4.") + except: + pass + + def update_footpath_thickness(self): + if self.deck_thickness.text() and not self.footpath_thickness.text(): + self.footpath_thickness.setText(self.deck_thickness.text()) + + def on_crash_barrier_type_changed(self, barrier_type): + if (barrier_type in ["Flexible", "Semi-Rigid"]) and (self.footpath_value == "None"): + QMessageBox.critical(self, "Crash Barrier Type Not Permitted", + f"{barrier_type} crash barriers are not permitted on bridges without an outer footpath per IRC 5 Clause 109.6.4.") + + def on_railing_load_mode_changed(self, mode): + if not hasattr(self, "railing_load_value"): + return + is_auto = mode.startswith("Automatic") + self.railing_load_value.setEnabled(not is_auto) + if is_auto: + self.railing_load_value.clear() + + def on_lane_count_changed(self, text): + self._update_lane_details_rows(text) + + def _update_lane_details_rows(self, count): + try: + total_rows = int(count) + except (TypeError, ValueError): + total_rows = 1 + if not hasattr(self, "lane_table"): + return + self.lane_table.setRowCount(total_rows) + for row in range(total_rows): + lane_item = QTableWidgetItem(str(row + 1)) + lane_item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable) + self.lane_table.setItem(row, 0, lane_item) + for col in range(1, self.lane_table.columnCount()): + existing_item = self.lane_table.item(row, col) + if existing_item is None: + self.lane_table.setItem(row, col, QTableWidgetItem("")) + + def _show_placeholder_message(self, action_name): + QMessageBox.information(self, action_name, "This action will be available in an upcoming update.") + +class SectionPropertiesTab(QWidget): + """Sub-tab for Section Properties with custom navigation layout.""" + + def __init__(self, parent=None): + super().__init__(parent) + self.nav_buttons = [] + self.init_ui() + + def init_ui(self): + """Initialize styled navigation and content panels.""" + self.setStyleSheet("background-color: #f0f0f0;") + main_layout = QVBoxLayout(self) + main_layout.setContentsMargins(10, 10, 10, 10) + main_layout.setSpacing(10) + + # Top navigation bar (horizontal) + nav_bar = QWidget() + nav_bar.setStyleSheet("background-color: white;") + nav_bar_layout = QHBoxLayout(nav_bar) + nav_bar_layout.setContentsMargins(0, 0, 0, 0) + nav_bar_layout.setSpacing(0) + + main_layout.addWidget(nav_bar) + + # Content frame + content_frame = QFrame() + content_frame.setObjectName("sectionContentFrame") + content_frame.setStyleSheet(""" + QFrame#sectionContentFrame { + background-color: #f0f0f0; + border: none; + } + """) + content_inner_layout = QVBoxLayout(content_frame) + content_inner_layout.setContentsMargins(0, 0, 0, 0) + content_inner_layout.setSpacing(0) + + self.stack = QStackedWidget() + self.stack.setObjectName("sectionStack") + self.stack.setStyleSheet("QStackedWidget#sectionStack { background-color: transparent; }") + content_inner_layout.addWidget(self.stack) + + main_layout.addWidget(content_frame, 1) + + sections = [ + ("Girder Details:", GirderDetailsTab), + ("Stiffener Details:", StiffenerDetailsTab), + ("Cross-Bracing Details:", CrossBracingDetailsTab), + ("End Diaphragm Details:", EndDiaphragmDetailsTab), + ] + + for i, (label, widget_class) in enumerate(sections): + btn = QPushButton(label) + btn.setObjectName("sectionNavBtn") + btn.setCheckable(True) + btn.setStyleSheet(""" + QPushButton#sectionNavBtn { + background-color: white; + color: #333; + border: 1px solid #b0b0b0; + border-right: none; + padding: 6px 14px; + text-align: center; + font-size: 10px; + font-weight: normal; + min-height: 26px; + } + QPushButton#sectionNavBtn:first { + border-top-left-radius: 5px; + border-bottom-left-radius: 5px; + } + QPushButton#sectionNavBtn:last { + border-right: 1px solid #b0b0b0; + border-top-right-radius: 5px; + border-bottom-right-radius: 5px; + } + QPushButton#sectionNavBtn:checked { + background-color: #90AF13; + color: white; + font-weight: bold; + border: 1px solid #90AF13; + } + QPushButton#sectionNavBtn:hover:!checked { + background-color: #f5f5f5; + } + """) + btn.clicked.connect(lambda checked, idx=i: self.switch_section(idx)) + self.nav_buttons.append(btn) + nav_bar_layout.addWidget(btn) + + section_widget = widget_class() + self.stack.addWidget(section_widget) + + if self.nav_buttons: + self.nav_buttons[0].setChecked(True) + self.stack.setCurrentIndex(0) + + action_bar, self.defaults_button, self.save_button = create_action_button_bar() + main_layout.addSpacing(6) + main_layout.addWidget(action_bar) + + def switch_section(self, index): + """Switch the stacked widget page and update navigation states.""" + self.stack.setCurrentIndex(index) + for btn_index, button in enumerate(self.nav_buttons): + button.setChecked(btn_index == index) + + +class GirderDetailsTab(QWidget): + """Tab for Girder Details styled to match the provided reference.""" + + def __init__(self, parent=None): + super().__init__(parent) + self.welded_rows = [] + self.rolled_rows = [] + self.section_property_inputs = {} + self.init_ui() + + def init_ui(self): + main_layout = QVBoxLayout(self) + main_layout.setContentsMargins(0, 0, 0, 0) + main_layout.setSpacing(0) + + scroll = QScrollArea() + scroll.setWidgetResizable(True) + scroll.setFrameShape(QFrame.NoFrame) + scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + scroll.setStyleSheet("QScrollArea { border: none; background: transparent; }") + main_layout.addWidget(scroll) + + content = QWidget() + scroll.setWidget(content) + content.setStyleSheet("background-color: white;") + + content_layout = QVBoxLayout(content) + content_layout.setContentsMargins(10, 0, 10, 10) + content_layout.setSpacing(12) + + content_layout.addWidget(self._build_overview_card()) + content_layout.addWidget(self._build_section_card()) + content_layout.addStretch() + + def _build_overview_card(self): + card = self._create_card_frame() + layout = QGridLayout(card) + layout.setContentsMargins(18, 16, 18, 16) + layout.setHorizontalSpacing(20) + layout.setVerticalSpacing(12) + + self.select_girder_combo = QComboBox() + self.select_girder_combo.addItems(["Girder 1", "Girder 2", "Girder 3", "Girder 4", "Girder 5", "All"]) + apply_field_style(self.select_girder_combo) + self._set_field_width(self.select_girder_combo) + + self.span_combo = QComboBox() + self.span_combo.addItems(["Custom", "Full Length"]) + apply_field_style(self.span_combo) + self._set_field_width(self.span_combo) + + self.member_id_input = QLineEdit("G1-1") + apply_field_style(self.member_id_input) + self._set_field_width(self.member_id_input) + + self.member_select_combo = QComboBox() + self.member_select_combo.addItems(["Girder 1", "Girder 2", "Girder 3", "Girder 4", "Girder 5"]) + apply_field_style(self.member_select_combo) + self._set_field_width(self.member_select_combo) + + self.distance_start_input = QLineEdit("0") + self.distance_end_input = QLineEdit("30") + apply_field_style(self.distance_start_input) + apply_field_style(self.distance_end_input) + self._set_field_width(self.distance_start_input, 80) + self._set_field_width(self.distance_end_input, 80) + + self.length_input = QLineEdit("30") + apply_field_style(self.length_input) + self._set_field_width(self.length_input) + + layout.addWidget(self._create_label("Select Girder:"), 0, 0) + layout.addWidget(self.select_girder_combo, 0, 1) + layout.addWidget(self._create_label("Span:"), 0, 2) + layout.addWidget(self.span_combo, 0, 3) + + layout.addWidget(self._create_label("Member ID:"), 1, 0) + layout.addWidget(self.member_id_input, 1, 1) + layout.addWidget(self._create_label("Length (m):"), 1, 2) + layout.addWidget(self.length_input, 1, 3) + + layout.addWidget(self._create_label("Distance from left edge (m):"), 2, 0) + layout.addLayout(self._build_distance_row(), 2, 1) + + layout.addWidget(self._create_label("Member:"), 2, 2) + layout.addWidget(self.member_select_combo, 2, 3) + + return card + + def _build_distance_row(self): + row = QHBoxLayout() + row.setContentsMargins(0, 0, 0, 0) + row.setSpacing(8) + row.addWidget(self._create_small_label("Start")) + row.addWidget(self.distance_start_input) + row.addWidget(self._create_small_label("End")) + row.addWidget(self.distance_end_input) + return row + + def _build_section_card(self): + container = QWidget() + container.setStyleSheet("background: transparent;") + main_layout = QHBoxLayout(container) + main_layout.setContentsMargins(0, 0, 0, 0) + main_layout.setSpacing(16) + + # Left side - two bordered boxes stacked vertically + left_column = QWidget() + left_column.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) + left_column_layout = QVBoxLayout(left_column) + left_column_layout.setContentsMargins(0, 0, 0, 0) + left_column_layout.setSpacing(12) + + # Section Inputs box (single frame containing all fields) + section_inputs_box = self._create_inner_box() + section_inputs_layout = QVBoxLayout(section_inputs_box) + section_inputs_layout.setContentsMargins(12, 8, 12, 12) + section_inputs_layout.setSpacing(8) + + section_inputs_title = self._create_label("Section Inputs:") + section_inputs_layout.addWidget(section_inputs_title) + + inputs_grid = QGridLayout() + inputs_grid.setContentsMargins(0, 0, 0, 0) + inputs_grid.setHorizontalSpacing(16) + inputs_grid.setVerticalSpacing(12) + inputs_grid.setColumnMinimumWidth(0, 150) + inputs_grid.setColumnStretch(0, 0) + inputs_grid.setColumnStretch(1, 1) + + self.design_combo = QComboBox() + self.design_combo.addItems(["Customized", "Optimized"]) + apply_field_style(self.design_combo) + row = self._add_box_row(inputs_grid, 0, "Design:", self.design_combo) + + self.type_combo = QComboBox() + self.type_combo.addItems(["Welded", "Rolled"]) + apply_field_style(self.type_combo) + row = self._add_box_row(inputs_grid, row, "Type:", self.type_combo) + + self.symmetry_combo = QComboBox() + self.symmetry_combo.addItems(["Girder Symmetric", "Girder Unsymmetric"]) + apply_field_style(self.symmetry_combo) + row = self._add_box_row(inputs_grid, row, "Symmetry:", self.symmetry_combo) + + self.total_depth_input = self._create_line_edit() + row = self._add_box_row(inputs_grid, row, "Total Depth (mm):", self.total_depth_input, self.welded_rows) + + self.web_thickness_combo = QComboBox() + self.web_thickness_combo.addItems(["All", "Custom"]) + apply_field_style(self.web_thickness_combo) + row = self._add_box_row(inputs_grid, row, "Web Thickness (mm):", self.web_thickness_combo, self.welded_rows) + + self.top_width_input = self._create_line_edit() + row = self._add_box_row(inputs_grid, row, "Width of Top Flange (mm):", self.top_width_input, self.welded_rows) + + self.top_thickness_combo = QComboBox() + self.top_thickness_combo.addItems(["All", "Custom"]) + apply_field_style(self.top_thickness_combo) + row = self._add_box_row(inputs_grid, row, "Top Flange Thickness (mm):", self.top_thickness_combo, self.welded_rows) + + self.bottom_width_input = self._create_line_edit() + row = self._add_box_row(inputs_grid, row, "Width of Bottom Flange (mm):", self.bottom_width_input, self.welded_rows) + + self.bottom_thickness_combo = QComboBox() + self.bottom_thickness_combo.addItems(["All", "Custom"]) + apply_field_style(self.bottom_thickness_combo) + row = self._add_box_row(inputs_grid, row, "Bottom Flange Thickness (mm):", self.bottom_thickness_combo, self.welded_rows) + + self.is_section_combo = QComboBox() + self.is_section_combo.addItems([ + "ISMB 500", "ISMB 550", "ISMB 600", + "ISWB 500", "ISWB 550", "ISWB 600" + ]) + apply_field_style(self.is_section_combo) + self._add_box_row(inputs_grid, row, "IS Section:", self.is_section_combo, self.rolled_rows) + + section_inputs_layout.addLayout(inputs_grid) + left_column_layout.addWidget(section_inputs_box) + + # Restraint/Web details box + restraint_box = self._create_inner_box() + restraint_layout = QVBoxLayout(restraint_box) + restraint_layout.setContentsMargins(12, 8, 12, 12) + restraint_layout.setSpacing(8) + + restraint_title = self._create_label("Restraint & Web Details:") + restraint_layout.addWidget(restraint_title) + + restraint_grid = QGridLayout() + restraint_grid.setContentsMargins(0, 0, 0, 0) + restraint_grid.setHorizontalSpacing(16) + restraint_grid.setVerticalSpacing(12) + restraint_grid.setColumnMinimumWidth(0, 150) + restraint_grid.setColumnStretch(0, 0) + restraint_grid.setColumnStretch(1, 1) + + self.torsion_combo = QComboBox() + self.torsion_combo.addItems(VALUES_TORSIONAL_RESTRAINT) + apply_field_style(self.torsion_combo) + row = self._add_box_row(restraint_grid, 0, "Torsional Restraint:", self.torsion_combo) + + self.warping_combo = QComboBox() + self.warping_combo.addItems(VALUES_WARPING_RESTRAINT) + apply_field_style(self.warping_combo) + row = self._add_box_row(restraint_grid, row, "Warping Restraint:", self.warping_combo) + + self.web_type_combo = QComboBox() + self.web_type_combo.addItems(["Thin Web with ITS", "Thick Web"]) + apply_field_style(self.web_type_combo) + self._add_box_row(restraint_grid, row, "Web Type*:", self.web_type_combo) + + restraint_layout.addLayout(restraint_grid) + left_column_layout.addWidget(restraint_box) + + main_layout.addWidget(left_column) + + # Right side - image + section properties box + right_column = QWidget() + right_column.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) + right_column_layout = QVBoxLayout(right_column) + right_column_layout.setContentsMargins(0, 0, 0, 0) + right_column_layout.setSpacing(12) + + # Dynamic image box + image_box = self._create_inner_box() + image_layout = QVBoxLayout(image_box) + image_layout.setContentsMargins(10, 10, 10, 10) + image_layout.setSpacing(5) + + self.dynamic_image_label = QLabel("Welded Girder") + self.dynamic_image_label.setAlignment(Qt.AlignCenter) + self.dynamic_image_label.setMinimumSize(240, 140) + self.dynamic_image_label.setStyleSheet("QLabel { border: 1px solid #d0d0d0; border-radius: 4px; background-color: #fafafa; font-weight: bold; color: #5b5b5b; }") + image_layout.addWidget(self.dynamic_image_label) + + right_column_layout.addWidget(image_box) + + # Section Properties box + props_box = self._create_inner_box() + props_layout = QVBoxLayout(props_box) + props_layout.setContentsMargins(12, 10, 12, 10) + props_layout.setSpacing(10) + + props_title = self._create_label("Section Properties:") + props_layout.addWidget(props_title) + + properties_grid = QGridLayout() + properties_grid.setContentsMargins(0, 0, 0, 0) + properties_grid.setHorizontalSpacing(12) + properties_grid.setVerticalSpacing(10) + properties_grid.setColumnMinimumWidth(0, 140) + properties_grid.setColumnStretch(0, 0) + properties_grid.setColumnStretch(1, 1) + + property_fields = [ + "Mass, M (Kg/m)", + "Sectional Area, a (cm2)", + "2nd Moment of Area, Iz (cm4)", + "2nd Moment of Area, Iy (cm4)", + "Radius of Gyration, rz (cm)", + "Radius of Gyration, ry (cm)", + "Elastic Modulus, Zz (cm3)", + "Elastic Modulus, Zy (cm3)", + "Plastic Modulus, Zuz (cm3)", + "Plastic Modulus, Zuy (cm3)", + "Torsion Constant, It (cm4)", + "Warping Constant, Iw (cm6)" + ] + + for index, text in enumerate(property_fields): + label = self._create_small_label(text) + line_edit = self._create_line_edit() + line_edit.setPlaceholderText("") + properties_grid.addWidget(label, index, 0) + properties_grid.addWidget(line_edit, index, 1) + self.section_property_inputs[text] = line_edit + + props_layout.addLayout(properties_grid) + right_column_layout.addWidget(props_box) + + main_layout.addWidget(right_column) + + self.type_combo.currentTextChanged.connect(self._on_type_changed) + self._on_type_changed(self.type_combo.currentText()) + + return container + + def _create_card_frame(self): + frame = QFrame() + frame.setObjectName("girderCard") + frame.setStyleSheet("QFrame#girderCard { background-color: white; border: 1px solid #cfcfcf; border-radius: 10px; }") + return frame + + def _create_label(self, text): + label = QLabel(text) + label.setStyleSheet("font-size: 12px; color: #2f2f2f; font-weight: 600; background: transparent;") + label.setAutoFillBackground(False) + return label + + def _create_small_label(self, text): + label = QLabel(text) + label.setStyleSheet("font-size: 10px; color: #5a5a5a; background: transparent;") + label.setAutoFillBackground(False) + return label + + def _create_line_edit(self): + line_edit = QLineEdit() + apply_field_style(line_edit) + return line_edit + + def _add_section_row(self, layout, row, text, widget, tracker=None): + label = self._create_label(text) + widget.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) + self._set_field_width(widget) + layout.addWidget(label, row, 0) + layout.addWidget(widget, row, 1) + if tracker is not None: + tracker.append((label, widget)) + return row + 1 + + def _set_field_width(self, widget, width=230): + widget.setMaximumWidth(width) + widget.setMinimumWidth(min(width, 160)) + + def _on_type_changed(self, text): + is_welded = text.lower() == "welded" + self._set_row_visibility(self.welded_rows, is_welded) + self._set_row_visibility(self.rolled_rows, not is_welded) + if is_welded: + self.dynamic_image_label.setText("Welded Girder") + else: + self.dynamic_image_label.setText("Rolled Section") + + def _create_inner_box(self): + """Create a bordered box for grouped controls""" + box = QFrame() + box.setStyleSheet( + "QFrame {" + " border: 1px solid #b0b0b0;" + " border-radius: 6px;" + " background-color: #ffffff;" + "}" + "QFrame QComboBox, QFrame QLineEdit {" + " border: none;" + " border-bottom: 1px solid #d0d0d0;" + " border-radius: 0px;" + " min-height: 28px;" + " padding: 4px 8px;" + " background-color: #ffffff;" + "}" + "QFrame QComboBox:hover, QFrame QLineEdit:hover {" + " border-bottom: 1px solid #5d5d5d;" + "}" + "QFrame QComboBox:focus, QFrame QLineEdit:focus {" + " border-bottom: 1px solid #90AF13;" + "}" + "QFrame QLabel {" + " border: none;" + " padding: 0px;" + " margin: 0px;" + "}" + ) + return box + + def _create_small_label(self, text): + """Create a smaller label for compact layouts""" + label = QLabel(text) + label.setStyleSheet( + "QLabel {" + " color: #2b2b2b;" + " font-size: 11px;" + " font-weight: 500;" + " background: transparent;" + " border: none;" + " padding: 0px;" + " margin: 0px;" + "}" + ) + label.setAutoFillBackground(False) + return label + + def _add_box_row(self, layout, row, label_text, widget, visibility_list=None): + """Add a row to a box grid layout""" + label = self._create_small_label(label_text) + layout.addWidget(label, row, 0, Qt.AlignLeft | Qt.AlignVCenter) + layout.addWidget(widget, row, 1) + if visibility_list is not None: + visibility_list.append((label, widget)) + return row + 1 + + def _set_row_visibility(self, rows, visible): + for label, widget in rows: + label.setVisible(visible) + widget.setVisible(visible) + + +class StiffenerDetailsTab(QWidget): + """Tab for Stiffener Details with compact layout""" + + def __init__(self, parent=None): + super().__init__(parent) + self.init_ui() + + def init_ui(self): + main_layout = QVBoxLayout(self) + main_layout.setContentsMargins(0, 0, 0, 0) + main_layout.setSpacing(0) + + scroll = QScrollArea() + scroll.setWidgetResizable(True) + scroll.setFrameShape(QFrame.NoFrame) + scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + scroll.setStyleSheet( + "QScrollArea { border: none; background: transparent; }" + "QScrollArea > QWidget > QWidget { background: transparent; }" + ) + main_layout.addWidget(scroll) + + container = QWidget() + scroll.setWidget(container) + container.setStyleSheet("background-color: #f4f4f4;") + + container_layout = QVBoxLayout(container) + container_layout.setContentsMargins(0, 0, 0, 0) + container_layout.setSpacing(4) + + # Combined card for inputs and description + card_frame = self._create_card_frame() + card_layout = QHBoxLayout(card_frame) + card_layout.setContentsMargins(18, 16, 18, 16) + card_layout.setSpacing(18) + + # Left column - inputs + left_column = QWidget() + left_layout = QVBoxLayout(left_column) + left_layout.setContentsMargins(0, 0, 0, 0) + left_layout.setSpacing(12) + + girder_row = QHBoxLayout() + girder_row.setContentsMargins(0, 0, 0, 0) + girder_row.setSpacing(10) + + girder_label = QLabel("Select Girder Member:") + girder_label.setStyleSheet("font-size: 11px; font-weight: 600; color: #3a3a3a; border: none;") + girder_row.addWidget(girder_label) + + self.girder_member_combo = QComboBox() + self.girder_member_combo.addItems(["G1-1", "G1-2", "G1-3", "All"]) + apply_field_style(self.girder_member_combo) + self.girder_member_combo.setFixedWidth(190) + self.girder_member_combo.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) + girder_row.addWidget(self.girder_member_combo, 1) + + left_layout.addLayout(girder_row) + + stiffener_heading = QLabel("Stiffener Inputs") + stiffener_heading.setStyleSheet("font-size: 11px; font-weight: 700; color: #000000; border: none; margin-top: 4px;") + left_layout.addWidget(stiffener_heading) + + inputs_grid = QGridLayout() + inputs_grid.setContentsMargins(0, 0, 0, 0) + inputs_grid.setHorizontalSpacing(12) + inputs_grid.setVerticalSpacing(10) + inputs_grid.setColumnMinimumWidth(0, 180) + inputs_grid.setColumnStretch(0, 0) + inputs_grid.setColumnStretch(1, 1) + + self.intermediate_combo = QComboBox() + self.intermediate_combo.addItems(["No", "Yes - At Supports", "Yes - Spaced"]) + apply_field_style(self.intermediate_combo) + row = self._add_form_row(inputs_grid, 0, "Intermediate Stiffener:", self.intermediate_combo) + + self.spacing_field = OptimizableField("Intermediate Stiffener Spacing") + self.spacing_field.mode_combo.clear() + self.spacing_field.mode_combo.addItems(["NA", "Optimized", "Customized"]) + self.spacing_field.on_mode_changed(self.spacing_field.mode_combo.currentText()) + self._prepare_optimizable_field(self.spacing_field) + row = self._add_form_row(inputs_grid, row, "Intermediate Stiffener Spacing:", self.spacing_field) + + self.longitudinal_combo = QComboBox() + self.longitudinal_combo.addItems(["None", "Yes and 1 stiffener", "Yes and 2 stiffeners"]) + apply_field_style(self.longitudinal_combo) + row = self._add_form_row(inputs_grid, row, "Longitudinal Stiffener:", self.longitudinal_combo) + + self.intermediate_thick_combo = QComboBox() + self.intermediate_thick_combo.addItems(["All", "Custom"]) + apply_field_style(self.intermediate_thick_combo) + row = self._add_form_row(inputs_grid, row, "Intermediate Stiffener Thickness:", self.intermediate_thick_combo) + + self.long_thick_combo = QComboBox() + self.long_thick_combo.addItems(["All", "Custom"]) + self.long_thick_combo.setEnabled(False) + apply_field_style(self.long_thick_combo) + row = self._add_form_row(inputs_grid, row, "Longitudinal Stiffener Thickness:", self.long_thick_combo) + + left_layout.addLayout(inputs_grid) + + buckling_heading = QLabel("Web Buckling Details") + buckling_heading.setStyleSheet("font-size: 11px; font-weight: 700; color: #000000; border: none; margin-top: 4px;") + left_layout.addWidget(buckling_heading) + + buckling_grid = QGridLayout() + buckling_grid.setContentsMargins(0, 0, 0, 0) + buckling_grid.setHorizontalSpacing(12) + buckling_grid.setVerticalSpacing(10) + buckling_grid.setColumnMinimumWidth(0, 180) + + self.method_combo = QComboBox() + self.method_combo.addItems(VALUES_STIFFENER_DESIGN) + apply_field_style(self.method_combo) + self._add_form_row(buckling_grid, 0, "Shear Buckling Design Method:", self.method_combo) + + left_layout.addLayout(buckling_grid) + + card_layout.addWidget(left_column, 2) + + # Right column - description + right_column = QWidget() + right_layout = QVBoxLayout(right_column) + right_layout.setContentsMargins(0, 0, 0, 0) + right_layout.setSpacing(8) + + desc_heading = QLabel("Description") + desc_heading.setStyleSheet("font-size: 11px; font-weight: 700; color: #000000; border: none;") + right_layout.addWidget(desc_heading) + + self.description_text = QTextEdit() + self.description_text.setReadOnly(True) + self.description_text.setPlaceholderText("Describe stiffener assumptions or notes here.") + self.description_text.setMinimumHeight(210) + self.description_text.setStyleSheet( + "QTextEdit { border: 1px solid #d0d0d0; border-radius: 6px; background: #ffffff; color: #3a3a3a; font-size: 11px; }" + ) + right_layout.addWidget(self.description_text, 1) + + card_layout.addWidget(right_column, 3) + + container_layout.addWidget(card_frame) + + # Dynamic image box + image_box = self._create_card_frame() + image_layout = QVBoxLayout(image_box) + image_layout.setContentsMargins(16, 16, 16, 16) + image_layout.setSpacing(8) + + self.dynamic_image_label = QLabel("Dynamic Image") + self.dynamic_image_label.setAlignment(Qt.AlignCenter) + self.dynamic_image_label.setMinimumHeight(140) + self.dynamic_image_label.setStyleSheet( + "QLabel { border: 1px solid #d8d8d8; border-radius: 8px; background-color: #f8f8f8; " + "font-weight: 600; color: #5b5b5b; font-size: 11px; }" + ) + image_layout.addWidget(self.dynamic_image_label) + container_layout.addWidget(image_box) + + + # Signals + self.longitudinal_combo.currentTextChanged.connect(self.on_longitudinal_changed) + + def _create_card_frame(self): + card = QFrame() + card.setStyleSheet( + "QFrame { border: 1px solid #d6d6d6; border-radius: 8px; background-color: #f7f7f7; }" + ) + return card + + def _create_label(self, text): + label = QLabel(text) + label.setStyleSheet("font-size: 11px; color: #3a3a3a; border: none;") + return label + + def _add_form_row(self, layout, row, text, widget): + label = self._create_label(text) + layout.addWidget(label, row, 0, Qt.AlignLeft | Qt.AlignVCenter) + widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) + layout.addWidget(widget, row, 1) + return row + 1 + + def _prepare_optimizable_field(self, field): + field.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) + apply_field_style(field.mode_combo) + apply_field_style(field.input_field) + + def on_longitudinal_changed(self, text): + has_longitudinal = text.lower().startswith("yes") + self.long_thick_combo.setEnabled(has_longitudinal) + +class CrossBracingDetailsTab(QWidget): + """Tab for Cross-Bracing Details with visual previews""" + + def __init__(self, parent=None): + super().__init__(parent) + self.init_ui() + + def init_ui(self): + main_layout = QVBoxLayout(self) + main_layout.setContentsMargins(0, 0, 0, 0) + main_layout.setSpacing(0) + + scroll = QScrollArea() + scroll.setWidgetResizable(True) + scroll.setFrameShape(QFrame.NoFrame) + scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + scroll.setStyleSheet( + "QScrollArea { border: none; background: transparent; }" + "QScrollArea > QWidget > QWidget { background: transparent; }" + ) + main_layout.addWidget(scroll) + + container = QWidget() + scroll.setWidget(container) + + container_layout = QVBoxLayout(container) + container_layout.setContentsMargins(0, 0, 0, 0) + container_layout.setSpacing(16) + + primary_card = self._create_card_frame() + card_layout = QHBoxLayout(primary_card) + card_layout.setContentsMargins(12, 10, 12, 10) + card_layout.setSpacing(10) + + # Left column (inputs) + left_column = QWidget() + left_column.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) + left_layout = QVBoxLayout(left_column) + left_layout.setContentsMargins(0, 0, 0, 0) + left_layout.setSpacing(4) + + selection_box = self._create_inner_box() + selection_layout = QGridLayout(selection_box) + selection_layout.setContentsMargins(8, 4, 8, 4) + selection_layout.setHorizontalSpacing(8) + selection_layout.setVerticalSpacing(4) + selection_layout.setColumnMinimumWidth(0, 130) + selection_layout.setColumnStretch(1, 1) + + self.select_girders_combo = QComboBox() + self.select_girders_combo.addItems(["G1 to G2", "G3 to G4", "All"]) + apply_field_style(self.select_girders_combo) + selection_layout.addWidget(self._create_label("Select Girders:"), 0, 0) + selection_layout.addWidget(self.select_girders_combo, 0, 1) + + self.member_id_combo = QComboBox() + self.member_id_combo.addItems(["B1-1 to B1-15", "B2-1 to B2-10", "Custom"]) + apply_field_style(self.member_id_combo) + selection_layout.addWidget(self._create_label("Member ID:"), 1, 0) + selection_layout.addWidget(self.member_id_combo, 1, 1) + + left_layout.addWidget(selection_box) + + inputs_box = self._create_inner_box() + inputs_box.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) + inputs_layout = QVBoxLayout(inputs_box) + inputs_layout.setContentsMargins(12, 8, 12, 8) + inputs_layout.setSpacing(6) + inputs_layout.addWidget(self._create_heading_label("Section Inputs:")) + + inputs_grid = QGridLayout() + inputs_grid.setContentsMargins(0, 0, 0, 0) + inputs_grid.setHorizontalSpacing(16) + inputs_grid.setVerticalSpacing(12) + inputs_grid.setColumnMinimumWidth(0, 130) + inputs_grid.setColumnStretch(0, 0) + inputs_grid.setColumnStretch(1, 1) + + self.design_combo = QComboBox() + self.design_combo.addItems(["Customized", "Optimized"]) + apply_field_style(self.design_combo) + row = self._add_grid_row(inputs_grid, 0, "Design:", self.design_combo) + + self.bracing_type_combo = QComboBox() + self.bracing_type_combo.addItems(["K-Bracing", "X-Bracing", "Diagonal", "Horizontal"]) + apply_field_style(self.bracing_type_combo) + row = self._add_grid_row(inputs_grid, row, "Type of Bracing:", self.bracing_type_combo) + + section_options = [ + "ISA 50 x 50 x 6", "ISA 65 x 65 x 6", "ISA 75 x 75 x 6", + "ISA 90 x 90 x 8", "ISA 100 x 100 x 8", "ISA 110 x 110 x 10", + "ISA 130 x 130 x 10", "ISMC 75", "ISMC 100", "ISMC 125", + "ISMC 150", "2-ISA 65 x 65 x 6", "2-ISA 75 x 75 x 6" + ] + + self.bracing_section_combo = QComboBox() + self.bracing_section_combo.addItems(section_options) + apply_field_style(self.bracing_section_combo) + row = self._add_grid_row(inputs_grid, row, "Bracing Section:", self.bracing_section_combo) + + self.top_bracket_type_combo = QComboBox() + self.top_bracket_type_combo.addItems(["Double Angles", "Single Angle", "Channel"]) + apply_field_style(self.top_bracket_type_combo) + row = self._add_grid_row(inputs_grid, row, "Top Bracket Section:", self.top_bracket_type_combo) + + self.top_bracket_size_combo = QComboBox() + self.top_bracket_size_combo.addItems(section_options) + apply_field_style(self.top_bracket_size_combo) + row = self._add_grid_row(inputs_grid, row, "Top Bracket Size:", self.top_bracket_size_combo) + + self.bottom_bracket_type_combo = QComboBox() + self.bottom_bracket_type_combo.addItems(["Double Angles", "Single Angle", "Channel"]) + apply_field_style(self.bottom_bracket_type_combo) + row = self._add_grid_row(inputs_grid, row, "Bottom Bracket Section:", self.bottom_bracket_type_combo) + + self.bottom_bracket_size_combo = QComboBox() + self.bottom_bracket_size_combo.addItems(section_options) + apply_field_style(self.bottom_bracket_size_combo) + row = self._add_grid_row(inputs_grid, row, "Bottom Bracket Size:", self.bottom_bracket_size_combo) + + self.spacing_input = QLineEdit() + self.spacing_input.setPlaceholderText("Spacing (mm)") + self.spacing_input.setValidator(QDoubleValidator(0, 100000, 2)) + apply_field_style(self.spacing_input) + self._add_grid_row(inputs_grid, row, "Spacing:", self.spacing_input) + + inputs_layout.addLayout(inputs_grid) + left_layout.addWidget(inputs_box) + + card_layout.addWidget(left_column) + + # Right column (previews) + right_column = QWidget() + right_column.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) + right_layout = QVBoxLayout(right_column) + right_layout.setContentsMargins(0, 0, 0, 0) + right_layout.setSpacing(14) + + type_box = self._create_inner_box() + type_layout = QVBoxLayout(type_box) + type_layout.setContentsMargins(12, 10, 12, 10) + type_layout.setSpacing(10) + type_layout.addWidget(self._create_heading_label("Type of Bracing")) + self.bracing_image_label = self._create_image_placeholder(210) + type_layout.addWidget(self.bracing_image_label) + right_layout.addWidget(type_box) + + self.bracing_preview_box, self.bracing_preview_label = self._create_preview_box("Bracing") + right_layout.addWidget(self.bracing_preview_box) + + self.top_bracket_preview_box, self.top_bracket_preview_label = self._create_preview_box("Top Bracket") + right_layout.addWidget(self.top_bracket_preview_box) + + self.bottom_bracket_preview_box, self.bottom_bracket_preview_label = self._create_preview_box("Bottom Bracket") + right_layout.addWidget(self.bottom_bracket_preview_box) + + card_layout.addWidget(right_column) + card_layout.setStretch(0, 3) + card_layout.setStretch(1, 2) + container_layout.addWidget(primary_card) + container_layout.addStretch() + + self.bracing_type_combo.currentTextChanged.connect(self._update_previews) + self.bracing_section_combo.currentTextChanged.connect(self._update_previews) + self.top_bracket_size_combo.currentTextChanged.connect(self._update_previews) + self.bottom_bracket_size_combo.currentTextChanged.connect(self._update_previews) + self._update_previews() + + def _create_card_frame(self): + card = QFrame() + card.setStyleSheet("QFrame { border: 1px solid #d0d0d0; border-radius: 12px; background-color: #ffffff; }") + return card + + def _create_inner_box(self): + box = QFrame() + box.setStyleSheet( + "QFrame { border: 1px solid #cfcfcf; border-radius: 8px; background-color: #ffffff; }" + "QFrame QComboBox, QFrame QLineEdit { border: none; border-bottom: 1px solid #d0d0d0; border-radius: 0px; min-height: 28px; padding: 4px 8px; background-color: #ffffff; }" + "QFrame QComboBox:hover, QFrame QLineEdit:hover { border-bottom: 1px solid #5d5d5d; }" + "QFrame QComboBox:focus, QFrame QLineEdit:focus { border-bottom: 1px solid #90AF13; }" + "QFrame QLabel { border: none; }" + ) + return box + + def _create_heading_label(self, text): + label = QLabel(text) + label.setStyleSheet("font-size: 12px; font-weight: 600; color: #4b4b4b; border: none;") + return label + + def _create_label(self, text): + label = QLabel(text) + label.setStyleSheet("font-size: 11px; color: #4b4b4b; border: none;") + return label + + def _add_grid_row(self, layout, row, text, widget): + label = self._create_label(text) + layout.addWidget(label, row, 0, Qt.AlignLeft | Qt.AlignVCenter) + widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) + layout.addWidget(widget, row, 1) + return row + 1 + + def _create_image_placeholder(self, height): + label = QLabel("Bracing Preview") + label.setAlignment(Qt.AlignCenter) + label.setMinimumHeight(height) + label.setStyleSheet("QLabel { border: 1px solid #d0d0d0; border-radius: 10px; background-color: #f7f7f7; font-weight: bold; color: #5b5b5b; }") + return label + + def _create_preview_box(self, title): + box = self._create_inner_box() + layout = QVBoxLayout(box) + layout.setContentsMargins(12, 10, 12, 10) + layout.setSpacing(8) + layout.addWidget(self._create_heading_label(title)) + image = self._create_image_placeholder(120) + layout.addWidget(image) + return box, image + + def _update_previews(self): + self.bracing_image_label.setText(self.bracing_type_combo.currentText()) + self.bracing_preview_label.setText(self.bracing_section_combo.currentText()) + self.top_bracket_preview_label.setText(self.top_bracket_size_combo.currentText()) + self.bottom_bracket_preview_label.setText(self.bottom_bracket_size_combo.currentText()) + + +class EndDiaphragmDetailsTab(QWidget): + """Tab for End Diaphragm Details with type-specific layouts""" + + def __init__(self, parent=None): + super().__init__(parent) + self.init_ui() + + def init_ui(self): + main_layout = QVBoxLayout(self) + main_layout.setContentsMargins(0, 0, 0, 0) + main_layout.setSpacing(0) + + scroll = QScrollArea() + scroll.setWidgetResizable(True) + scroll.setFrameShape(QFrame.NoFrame) + scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + scroll.setStyleSheet( + "QScrollArea { border: none; background: transparent; }" + "QScrollArea > QWidget > QWidget { background: transparent; }" + ) + main_layout.addWidget(scroll) + + container = QWidget() + scroll.setWidget(container) + + container_layout = QVBoxLayout(container) + container_layout.setContentsMargins(0, 0, 0, 0) + container_layout.setSpacing(8) + + self.type_stack = QStackedWidget() + container_layout.addWidget(self.type_stack) + + self.views = {} + self.view_order = [] + self.type_selector_map = {} + self.type_selectors = [] + self.current_type = None + self.block_type_sync = False + + cross_view, cross_selector = self._build_cross_bracing_view() + self._add_type_view("Cross Bracing", cross_view, cross_selector) + rolled_view, rolled_selector = self._build_rolled_view() + self._add_type_view("Rolled Beam", rolled_view, rolled_selector) + welded_view, welded_selector = self._build_welded_view() + self._add_type_view("Welded Beam", welded_view, welded_selector) + + self._set_current_type("Cross Bracing") + + def _add_type_view(self, key, widget, type_selector): + self.views[key] = widget + self.view_order.append(key) + self.type_stack.addWidget(widget) + self.type_selector_map[key] = type_selector + self.type_selectors.append(type_selector) + type_selector.currentTextChanged.connect(self._handle_type_selection) + + # ---- Shared helpers ---- + def _create_card_frame(self): + card = QFrame() + card.setStyleSheet("QFrame { border: 1px solid #d0d0d0; border-radius: 12px; background-color: #ffffff; }") + return card + + def _create_inner_box(self): + box = QFrame() + box.setStyleSheet( + "QFrame { border: 1px solid #cfcfcf; border-radius: 8px; background-color: #ffffff; padding: 0px; margin: 0px; }" + "QFrame QComboBox, QFrame QLineEdit { border: none; border-bottom: 1px solid #d0d0d0; border-radius: 0px; min-height: 28px; padding: 4px 8px; background-color: #ffffff; }" + "QFrame QComboBox:hover, QFrame QLineEdit:hover { border-bottom: 1px solid #5d5d5d; }" + "QFrame QComboBox:focus, QFrame QLineEdit:focus { border-bottom: 1px solid #90AF13; }" + "QFrame QLabel { border: none; padding: 0px; margin: 0px; }" + ) + return box + + def _create_heading_label(self, text): + label = QLabel(text) + label.setStyleSheet("font-size: 12px; font-weight: 600; color: #4b4b4b; border: none; padding: 0px; margin: 0px;") + return label + + def _create_label(self, text): + label = QLabel(text) + label.setStyleSheet("font-size: 11px; color: #4b4b4b; border: none;") + return label + + def _add_grid_row(self, layout, row, text, widget): + label = self._create_label(text) + layout.addWidget(label, row, 0, Qt.AlignLeft | Qt.AlignVCenter) + widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) + layout.addWidget(widget, row, 1) + return row + 1 + + def _create_image_placeholder(self, text, min_height=140): + label = QLabel(text) + label.setAlignment(Qt.AlignCenter) + label.setMinimumHeight(min_height) + label.setStyleSheet("QLabel { border: 1px solid #d0d0d0; border-radius: 10px; background-color: #f7f7f7; font-weight: bold; color: #5b5b5b; }") + return label + + def _create_line_edit(self, placeholder=""): + line_edit = QLineEdit() + if placeholder: + line_edit.setPlaceholderText(placeholder) + apply_field_style(line_edit) + return line_edit + + def _create_selection_box(self): + box = self._create_inner_box() + box.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) + layout = QGridLayout(box) + layout.setContentsMargins(10, 8, 10, 8) + layout.setHorizontalSpacing(12) + layout.setVerticalSpacing(8) + layout.setColumnMinimumWidth(0, 120) + layout.setColumnStretch(1, 1) + + girders_combo = QComboBox() + girders_combo.addItems(["G1 to G2", "G3 to G4", "All"]) + apply_field_style(girders_combo) + layout.addWidget(self._create_label("Select Girders:"), 0, 0) + layout.addWidget(girders_combo, 0, 1) + + member_combo = QComboBox() + member_combo.addItems(["E1-1, E1-2", "E2-1, E2-2", "Custom"]) + apply_field_style(member_combo) + layout.addWidget(self._create_label("Member ID:"), 1, 0) + layout.addWidget(member_combo, 1, 1) + + return box + + def _create_section_properties_box(self, title): + box = self._create_inner_box() + layout = QVBoxLayout(box) + layout.setContentsMargins(12, 10, 12, 10) + layout.setSpacing(10) + layout.addWidget(self._create_heading_label(title)) + + grid = QGridLayout() + grid.setContentsMargins(0, 0, 0, 0) + grid.setHorizontalSpacing(12) + grid.setVerticalSpacing(10) + grid.setColumnMinimumWidth(0, 150) + grid.setColumnStretch(0, 0) + grid.setColumnStretch(1, 1) + + properties = [ + "Mass, M (Kg/m)", + "Sectional Area, a (cm2)", + "2nd Moment of Area, Iz (cm4)", + "2nd Moment of Area, Iy (cm4)", + "Radius of Gyration, rz (cm)", + "Radius of Gyration, ry (cm)", + "Elastic Modulus, Zz (cm3)", + "Elastic Modulus, Zy (cm3)", + "Plastic Modulus, Zuz (cm3)", + "Plastic Modulus, Zuy (cm3)" + ] + + inputs = {} + for row, name in enumerate(properties): + label = self._create_label(name) + field = self._create_line_edit() + grid.addWidget(label, row, 0) + grid.addWidget(field, row, 1) + inputs[name] = field + + layout.addLayout(grid) + return box, inputs + + # ---- View builders ---- + def _build_cross_bracing_view(self): + view = self._create_card_frame() + layout = QHBoxLayout(view) + layout.setContentsMargins(12, 12, 12, 12) + layout.setSpacing(12) + + left_column = QWidget() + left_layout = QVBoxLayout(left_column) + left_layout.setContentsMargins(0, 0, 0, 0) + left_layout.setSpacing(6) + left_layout.addWidget(self._create_selection_box()) + + inputs_box = self._create_inner_box() + inputs_layout = QVBoxLayout(inputs_box) + inputs_layout.setContentsMargins(12, 4, 12, 8) + inputs_layout.setSpacing(6) + title = self._create_heading_label("Section Inputs:") + title.setStyleSheet("font-size: 12px; font-weight: 600; color: #4b4b4b; border: none; margin-top: 0px; margin-bottom: 2px;") + inputs_layout.addWidget(title) + + grid = QGridLayout() + grid.setContentsMargins(0, 0, 0, 0) + grid.setHorizontalSpacing(12) + grid.setVerticalSpacing(10) + grid.setColumnMinimumWidth(0, 130) + grid.setColumnStretch(0, 0) + grid.setColumnStretch(1, 1) + + design_combo = QComboBox() + design_combo.addItems(["Customized", "Optimized"]) + apply_field_style(design_combo) + row = self._add_grid_row(grid, 0, "Design:", design_combo) + + type_selector = QComboBox() + type_selector.addItems(VALUES_END_DIAPHRAGM_TYPE) + type_selector.setCurrentText("Cross Bracing") + apply_field_style(type_selector) + row = self._add_grid_row(grid, row, "Type:", type_selector) + + bracing_combo = QComboBox() + bracing_combo.addItems(["K-Bracing", "X-Bracing", "Diagonal", "Horizontal"]) + apply_field_style(bracing_combo) + row = self._add_grid_row(grid, row, "Type of Bracing:", bracing_combo) + + section_options = [ + "Double Angles", "Single Angle", "Channel", + "ISA 100 x 100 x 8", "ISA 110 x 110 x 10" + ] + + bracing_section = QComboBox() + bracing_section.addItems(section_options) + apply_field_style(bracing_section) + row = self._add_grid_row(grid, row, "Bracing Section:", bracing_section) + + top_bracket = QComboBox() + top_bracket.addItems(section_options) + apply_field_style(top_bracket) + row = self._add_grid_row(grid, row, "Top Bracket Section:", top_bracket) + + bottom_bracket = QComboBox() + bottom_bracket.addItems(section_options) + apply_field_style(bottom_bracket) + row = self._add_grid_row(grid, row, "Bottom Bracket Section:", bottom_bracket) + + spacing_input = self._create_line_edit("Spacing (mm)") + spacing_input.setValidator(QDoubleValidator(0, 100000, 2)) + self._add_grid_row(grid, row, "Spacing:", spacing_input) + + inputs_layout.addLayout(grid) + inputs_box.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) + left_layout.addWidget(inputs_box) + left_layout.addStretch() + + layout.addWidget(left_column) + + right_column = QWidget() + right_layout = QVBoxLayout(right_column) + right_layout.setContentsMargins(0, 0, 0, 0) + right_layout.setSpacing(10) + + type_box = self._create_inner_box() + type_box.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) + type_layout = QVBoxLayout(type_box) + type_layout.setContentsMargins(12, 8, 12, 10) + type_layout.setSpacing(6) + type_layout.addWidget(self._create_heading_label("Type of Bracing")) + type_layout.addWidget(self._create_image_placeholder("Bracing Layout", 170)) + right_layout.addWidget(type_box) + + for title in ["Bracing", "Top Bracket", "Bottom Bracket"]: + preview_box = self._create_inner_box() + preview_box.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) + preview_layout = QVBoxLayout(preview_box) + preview_layout.setContentsMargins(12, 8, 12, 8) + preview_layout.setSpacing(6) + preview_layout.addWidget(self._create_heading_label(title)) + preview_layout.addWidget(self._create_image_placeholder("Preview", 110)) + right_layout.addWidget(preview_box) + + right_layout.addStretch() + layout.addWidget(right_column) + layout.setStretch(0, 3) + layout.setStretch(1, 4) + return view, type_selector + + def _build_rolled_view(self): + view = self._create_card_frame() + layout = QHBoxLayout(view) + layout.setContentsMargins(12, 12, 12, 12) + layout.setSpacing(12) + + left_column = QWidget() + left_layout = QVBoxLayout(left_column) + left_layout.setContentsMargins(0, 0, 0, 0) + left_layout.setSpacing(8) + left_layout.addWidget(self._create_selection_box()) + + inputs_box = self._create_inner_box() + inputs_layout = QVBoxLayout(inputs_box) + inputs_layout.setContentsMargins(12, 4, 12, 8) + inputs_layout.setSpacing(6) + title = self._create_heading_label("Section Inputs") + title.setStyleSheet("font-size: 12px; font-weight: 600; color: #4b4b4b; border: none; margin-top: 0px; margin-bottom: 2px;") + inputs_layout.addWidget(title) + + grid = QGridLayout() + grid.setContentsMargins(0, 0, 0, 0) + grid.setHorizontalSpacing(12) + grid.setVerticalSpacing(8) + grid.setColumnMinimumWidth(0, 130) + grid.setColumnStretch(0, 0) + grid.setColumnStretch(1, 1) + + design_combo = QComboBox() + design_combo.addItems(["Customized", "Optimized"]) + apply_field_style(design_combo) + row = self._add_grid_row(grid, 0, "Design:", design_combo) + + type_selector = QComboBox() + type_selector.addItems(VALUES_END_DIAPHRAGM_TYPE) + type_selector.setCurrentText("Rolled Beam") + apply_field_style(type_selector) + row = self._add_grid_row(grid, row, "Type:", type_selector) + + is_section_combo = QComboBox() + is_section_combo.addItems([ + "ISMB 500", "ISMB 550", "ISMB 600", + "ISWB 500", "ISWB 550", "ISWB 600" + ]) + apply_field_style(is_section_combo) + self._add_grid_row(grid, row, "IS Section:", is_section_combo) + + inputs_layout.addLayout(grid) + inputs_box.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) + left_layout.addWidget(inputs_box) + left_layout.addStretch() + layout.addWidget(left_column) + + right_column = QWidget() + right_layout = QVBoxLayout(right_column) + right_layout.setContentsMargins(0, 0, 0, 0) + right_layout.setSpacing(10) + + image_box = self._create_inner_box() + image_box.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) + image_layout = QVBoxLayout(image_box) + image_layout.setContentsMargins(12, 8, 12, 10) + image_layout.setSpacing(6) + image_layout.addWidget(self._create_heading_label("Dynamic Image")) + image_layout.addWidget(self._create_image_placeholder("Rolled Section", 170)) + right_layout.addWidget(image_box) + + props_box, _ = self._create_section_properties_box("Section Properties:") + props_box.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) + right_layout.addWidget(props_box) + right_layout.addStretch() + + layout.addWidget(right_column) + return view, type_selector + + def _build_welded_view(self): + view = self._create_card_frame() + layout = QHBoxLayout(view) + layout.setContentsMargins(12, 12, 12, 12) + layout.setSpacing(12) + + left_column = QWidget() + left_layout = QVBoxLayout(left_column) + left_layout.setContentsMargins(0, 0, 0, 0) + left_layout.setSpacing(8) + left_layout.addWidget(self._create_selection_box()) + + inputs_box = self._create_inner_box() + inputs_layout = QVBoxLayout(inputs_box) + inputs_layout.setContentsMargins(12, 8, 12, 10) + inputs_layout.setSpacing(8) + inputs_layout.addWidget(self._create_heading_label("Section Inputs:")) + + grid = QGridLayout() + grid.setContentsMargins(0, 0, 0, 0) + grid.setHorizontalSpacing(12) + grid.setVerticalSpacing(10) + grid.setColumnMinimumWidth(0, 150) + grid.setColumnStretch(0, 0) + grid.setColumnStretch(1, 1) + + design_combo = QComboBox() + design_combo.addItems(["Customized", "Optimized"]) + apply_field_style(design_combo) + row = self._add_grid_row(grid, 0, "Design:", design_combo) + + type_selector = QComboBox() + type_selector.addItems(VALUES_END_DIAPHRAGM_TYPE) + type_selector.setCurrentText("Welded Beam") + apply_field_style(type_selector) + row = self._add_grid_row(grid, row, "Type:", type_selector) + + symmetry_combo = QComboBox() + symmetry_combo.addItems(["Girder Symmetric", "Girder Unsymmetric"]) + apply_field_style(symmetry_combo) + row = self._add_grid_row(grid, row, "Symmetry:", symmetry_combo) + + total_depth = self._create_line_edit() + row = self._add_grid_row(grid, row, "Total Depth (mm):", total_depth) + + web_thick_combo = QComboBox() + web_thick_combo.addItems(["All", "Custom"]) + apply_field_style(web_thick_combo) + row = self._add_grid_row(grid, row, "Web Thickness (mm):", web_thick_combo) + + top_width = self._create_line_edit() + row = self._add_grid_row(grid, row, "Width of Top Flange (mm):", top_width) + + top_thickness_combo = QComboBox() + top_thickness_combo.addItems(["All", "Custom"]) + apply_field_style(top_thickness_combo) + row = self._add_grid_row(grid, row, "Top Flange Thickness (mm):", top_thickness_combo) + + bottom_width = self._create_line_edit() + row = self._add_grid_row(grid, row, "Width of Bottom Flange (mm):", bottom_width) + + bottom_thickness_combo = QComboBox() + bottom_thickness_combo.addItems(["All", "Custom"]) + apply_field_style(bottom_thickness_combo) + row = self._add_grid_row(grid, row, "Bottom Flange Thickness (mm):", bottom_thickness_combo) + + bearing_thickness = self._create_line_edit() + self._add_grid_row(grid, row, "Bearing Stiffener Thickness (mm):", bearing_thickness) + + inputs_layout.addLayout(grid) + inputs_box.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) + left_layout.addWidget(inputs_box) + left_layout.addStretch() + layout.addWidget(left_column) + + right_column = QWidget() + right_layout = QVBoxLayout(right_column) + right_layout.setContentsMargins(0, 0, 0, 0) + right_layout.setSpacing(10) + + image_box = self._create_inner_box() + image_box.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) + image_layout = QVBoxLayout(image_box) + image_layout.setContentsMargins(12, 8, 12, 10) + image_layout.setSpacing(6) + image_layout.addWidget(self._create_heading_label("Dynamic Image")) + image_layout.addWidget(self._create_image_placeholder("Welded Section", 170)) + right_layout.addWidget(image_box) + + props_box, _ = self._create_section_properties_box("Section Properties:") + props_box.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) + right_layout.addWidget(props_box) + right_layout.addStretch() + + layout.addWidget(right_column) + return view, type_selector + + def _handle_type_selection(self, value): + if self.block_type_sync: + return + if value in self.view_order: + self._set_current_type(value) + + def _set_current_type(self, target): + if target not in self.view_order: + return + if self.current_type == target: + return + self.current_type = target + index = self.view_order.index(target) + self.type_stack.setCurrentIndex(index) + self.block_type_sync = True + for selector in self.type_selectors: + selector.setCurrentText(target) + self.block_type_sync = False + + +class CustomVehicleDialog(QDialog): + """Dialog for adding or editing custom live load vehicles""" + + def __init__(self, parent=None): + super().__init__(parent) + self.setWindowTitle("Live Load Custom Vehicle Add/Edit") + self.setModal(True) + self.setMinimumWidth(420) + self.setMinimumHeight(500) + self.setStyleSheet(""" + QDialog { background-color: #f5f5f5; } + QLabel { color: #2b2b2b; font-size: 11px; background: transparent; } + QLineEdit { + background-color: #ffffff; + border: 1px solid #8a8a8a; + border-radius: 4px; + padding: 4px 8px; + min-height: 24px; + color: #2b2b2b; + } + QLineEdit:focus { border: 1px solid #5a5a5a; } + QLineEdit:read-only { background-color: #f0f0f0; color: #5a5a5a; } + QPushButton { + background-color: #ffffff; + color: #2b2b2b; + border: 1px solid #8a8a8a; + border-radius: 4px; + padding: 5px 12px; + min-width: 50px; + } + QPushButton:hover { background-color: #e8e8e8; } + QPushButton:pressed { background-color: #d8d8d8; } + QTableWidget { + background-color: #ffffff; + border: 1px solid #8a8a8a; + gridline-color: #d0d0d0; + color: #2b2b2b; + } + QTableWidget::item { padding: 4px; } + QHeaderView::section { + background-color: #f0f0f0; + color: #2b2b2b; + border: 1px solid #d0d0d0; + padding: 4px; + font-weight: 600; + } + """) + self.init_ui() + + def init_ui(self): + layout = QVBoxLayout(self) + layout.setContentsMargins(16, 16, 16, 16) + layout.setSpacing(12) + + # Vehicle Name row + name_row = QHBoxLayout() + name_row.setSpacing(10) + name_label = QLabel("Vehicle Name:") + name_label.setStyleSheet("font-weight: 600;") + self.vehicle_name_input = QLineEdit() + self.vehicle_name_input.setFixedWidth(120) + name_row.addWidget(name_label) + name_row.addWidget(self.vehicle_name_input) + name_row.addStretch() + layout.addLayout(name_row) + + # P# D# row with Add/Modify/Delete buttons + pd_button_row = QHBoxLayout() + pd_button_row.setSpacing(8) + + p_label = QLabel("P#") + self.P_input = QLineEdit() + self.P_input.setFixedWidth(50) + pd_button_row.addWidget(p_label) + pd_button_row.addWidget(self.P_input) + + d_label = QLabel("D#") + self.D_input = QLineEdit() + self.D_input.setFixedWidth(50) + pd_button_row.addWidget(d_label) + pd_button_row.addWidget(self.D_input) + + pd_button_row.addStretch() + + self.add_axle_button = QPushButton("Add") + self.modify_axle_button = QPushButton("Modify") + self.delete_axle_button = QPushButton("Delete") + pd_button_row.addWidget(self.add_axle_button) + pd_button_row.addWidget(self.modify_axle_button) + pd_button_row.addWidget(self.delete_axle_button) + + layout.addLayout(pd_button_row) + + # Table and diagram row + table_diagram_row = QHBoxLayout() + table_diagram_row.setSpacing(12) + + # Axle table + self.axle_table = QTableWidget() + self.axle_table.setColumnCount(3) + self.axle_table.setHorizontalHeaderLabels(["No.", "Load (kN)", "Spacing (m)"]) + self.axle_table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) + self.axle_table.verticalHeader().setVisible(False) + self.axle_table.setMinimumHeight(120) + self.axle_table.setMaximumHeight(140) + table_diagram_row.addWidget(self.axle_table, 1) + + # Axle diagram placeholder + axle_diagram = QLabel("Axle Layout Diagram") + axle_diagram.setAlignment(Qt.AlignCenter) + axle_diagram.setMinimumHeight(120) + axle_diagram.setStyleSheet(""" + QLabel { + border: 1px solid #8a8a8a; + border-radius: 4px; + background: #ffffff; + color: #6a6a6a; + font-size: 10px; + } + """) + table_diagram_row.addWidget(axle_diagram, 1) + + layout.addLayout(table_diagram_row) + + # Input fields grid + fields_grid = QGridLayout() + fields_grid.setContentsMargins(0, 8, 0, 0) + fields_grid.setHorizontalSpacing(12) + fields_grid.setVerticalSpacing(10) + fields_grid.setColumnMinimumWidth(0, 240) + + field_labels = [ + "Minimum nose to tail distance (m):", + "Width of Wheel, w (mm):", + "Minimum Clearance from Carriageway\nEdge, f (mm):", + "Minimum Clearance from Crossing Vehicles,\ng (mm):", + "Wheel Spacing in Transverse Direction (m):", + "Impact Factor:", + ] + + self.custom_fields = {} + for row, text in enumerate(field_labels): + lbl = QLabel(text) + field = QLineEdit() + if "Impact" in text: + field.setText("0.25") + field.setReadOnly(True) + field.setFixedWidth(100) + fields_grid.addWidget(lbl, row, 0, Qt.AlignLeft | Qt.AlignVCenter) + fields_grid.addWidget(field, row, 1, Qt.AlignLeft | Qt.AlignVCenter) + self.custom_fields[text] = field + + layout.addLayout(fields_grid) + + # Bottom diagram - Clear Carriageway Width + bottom_diagram_label = QLabel("CLEAR CARRIAGEWAY WIDTH") + bottom_diagram_label.setAlignment(Qt.AlignCenter) + bottom_diagram_label.setStyleSheet("font-size: 9px; font-weight: 600; color: #5a5a5a; background: transparent;") + layout.addWidget(bottom_diagram_label) + + bottom_diagram = QLabel("") + bottom_diagram.setAlignment(Qt.AlignCenter) + bottom_diagram.setMinimumHeight(80) + bottom_diagram.setStyleSheet(""" + QLabel { + border: 1px solid #8a8a8a; + border-radius: 4px; + background: #ffffff; + } + """) + layout.addWidget(bottom_diagram) + + layout.addStretch() + + +class LoadingTab(QWidget): + """Loading tab with permanent load layout and load-type subtabs""" + + def __init__(self, parent=None): + super().__init__(parent) + self.custom_vehicle_dialog = CustomVehicleDialog(self) + self.init_ui() + + def init_ui(self): + main_layout = QVBoxLayout(self) + main_layout.setContentsMargins(0, 0, 0, 0) + main_layout.setSpacing(0) + + self.load_tabs = QTabWidget() + self.load_tabs.setDocumentMode(True) + self.load_tabs.setStyleSheet( + "QTabWidget::pane { border: none; background: #f5f5f5; }" + "QTabBar::tab { background: #e8e8e8; color: #4b4b4b; border: 1px solid #cfcfcf;" + " border-bottom: none; padding: 8px 20px; margin-right: 2px; min-width: 120px;" + " font-size: 11px; border-top-left-radius: 6px; border-top-right-radius: 6px; }" + "QTabBar::tab:selected { background: #9ecb3d; color: #ffffff; font-weight: bold; }" + "QTabBar::tab:!selected { margin-top: 2px; }" + ) + + self.load_tabs.addTab(self._build_permanent_load_tab(), "Permanent Load") + self.load_tabs.addTab(self._build_live_load_tab(), "Live Load") + self.load_tabs.addTab(self._build_seismic_load_tab(), "Seismic Load") + self.load_tabs.addTab(self._build_wind_load_tab(), "Wind Load") + self.load_tabs.addTab(self._build_temperature_load_tab(), "Temperature Load") + self.load_tabs.addTab(self._create_placeholder_page("Custom Load"), "Custom Load") + self.load_tabs.addTab(self._build_load_combination_tab(), "Load Combination") + main_layout.addWidget(self.load_tabs) + + action_bar, self.defaults_button, self.save_button = create_action_button_bar() + main_layout.addSpacing(6) + main_layout.addWidget(action_bar) + + def _build_permanent_load_tab(self): + page = QWidget() + page.setStyleSheet("background-color: #f5f5f5;") + page_layout = QVBoxLayout(page) + page_layout.setContentsMargins(12, 12, 12, 12) + page_layout.setSpacing(12) + + content_row = QHBoxLayout() + content_row.setContentsMargins(0, 0, 0, 0) + content_row.setSpacing(16) + + left_card = self._create_card() + left_card.setStyleSheet( + "QFrame { border: 1px solid #b2b2b2; border-radius: 10px; background-color: #ffffff; }" + ) + left_layout = QVBoxLayout(left_card) + left_layout.setContentsMargins(16, 16, 16, 16) + left_layout.setSpacing(16) + + self._add_load_section(left_layout, "Dead Load (DL):", [ + ("Include Member Self Weight:", self._create_yes_no_combo()), + ("Self-weight factor:", self._create_line_edit()), + ("Include Concrete Deck Weight:", self._create_yes_no_combo()), + ]) + + self._add_load_section(left_layout, "Dead Load for Surfacing (DW):", [ + ("Include Load from Wearing Course:", self._create_yes_no_combo()), + ]) + + self._add_load_section(left_layout, "Super-Imposed Dead Load (SIDL):", [ + ("Include Load from Crash Barrier:", self._create_yes_no_combo()), + ("Include Load from Median:", self._create_yes_no_combo()), + ("Include Load from Railing:", self._create_yes_no_combo()), + ]) + + left_layout.addStretch() + + right_card = self._create_card() + right_card.setStyleSheet( + "QFrame { border: 1px solid #9c9c9c; border-radius: 10px; background-color: #c8c8c8; }" + ) + right_card.setMinimumWidth(270) + right_card.setMinimumHeight(360) + right_layout = QVBoxLayout(right_card) + right_layout.setContentsMargins(18, 18, 18, 18) + right_layout.setSpacing(12) + description_label = QLabel("Description Box") + description_label.setAlignment(Qt.AlignCenter) + description_label.setStyleSheet("font-size: 12px; font-weight: 700; color: #000000;") + description_label.setMinimumHeight(320) + right_layout.addWidget(description_label) + + content_row.addWidget(left_card, 3) + content_row.addWidget(right_card, 2) + + page_layout.addLayout(content_row) + page_layout.addSpacing(4) + return page + + def _build_live_load_tab(self): + page = QWidget() + page.setStyleSheet("background-color: #f5f5f5;") + page_layout = QVBoxLayout(page) + page_layout.setContentsMargins(12, 12, 12, 12) + page_layout.setSpacing(12) + + content_row = QHBoxLayout() + content_row.setContentsMargins(0, 0, 0, 0) + content_row.setSpacing(16) + + left_card = self._create_card() + left_card.setStyleSheet("QFrame { border: 1px solid #b2b2b2; border-radius: 10px; background-color: #ffffff; }") + left_layout = QVBoxLayout(left_card) + left_layout.setContentsMargins(14, 14, 14, 14) + left_layout.setSpacing(8) + + # Title without box + title = QLabel("Live Load (LL) Inputs:") + title.setStyleSheet("font-size: 12px; font-weight: 700; color: #3a3a3a; background: transparent; border: none;") + left_layout.addWidget(title) + + irc_vehicles = [ + "Class A", "Class 70R Wheeled", "Class 70R Tracked", + "Class AA Wheeled", "Class AA Tracked", "Class SV", "Fatigue Truck" + ] + self._add_checkbox_section(left_layout, "Vehicles from IRC 6:", irc_vehicles) + + # Custom Vehicle header with Add/Edit buttons + custom_header = QHBoxLayout() + custom_header.setSpacing(8) + custom_label = QLabel("Custom Vehicle:") + custom_label.setStyleSheet("font-size: 12px; font-weight: 700; color: #3a3a3a; background: transparent; border: none;") + custom_header.addWidget(custom_label) + custom_header.addStretch() + self.custom_vehicle_add_button = QPushButton("Add") + self.custom_vehicle_edit_button = QPushButton("Edit") + for btn in (self.custom_vehicle_add_button, self.custom_vehicle_edit_button): + btn.setFixedWidth(50) + btn.setStyleSheet( + "QPushButton { background: #ffffff; color: #2f2f2f; border: 1px solid #7a7a7a; border-radius: 4px; padding: 4px 10px; }" + "QPushButton:hover { background: #f0f0f0; }" + "QPushButton:pressed { background: #e0e0e0; }" + ) + custom_header.addWidget(btn) + left_layout.addLayout(custom_header) + + # Vehicle Name 1 and 2 as simple checkbox rows (like reference image 2) + self.custom_vehicle_checkboxes = [] + for index in range(2): + row_layout = QHBoxLayout() + row_layout.setContentsMargins(0, 0, 0, 0) + row_layout.setSpacing(8) + label = QLabel(f"Vehicle Name {index + 1}") + label.setStyleSheet("font-size: 11px; font-style: italic; color: #4b4b4b; background: transparent; border: none;") + checkbox = QCheckBox() + checkbox.setChecked(False) + row_layout.addWidget(label) + row_layout.addStretch() + row_layout.addWidget(checkbox) + left_layout.addLayout(row_layout) + self.custom_vehicle_checkboxes.append(checkbox) + + # Braking Load from Vehicles section - includes IRC vehicles + Vehicle Name 1/2 + braking_vehicles = irc_vehicles + ["Vehicle Name 1", "Vehicle Name 2"] + self._add_checkbox_section(left_layout, "Braking Load from Vehicles:", braking_vehicles) + + # Bottom inputs with aligned widths + input_width = 120 + + # Eccentricity row + ecc_row = QHBoxLayout() + ecc_row.setSpacing(10) + ecc_label = QLabel("Eccentricity from top of Deck (m):") + ecc_label.setStyleSheet("font-size: 11px; font-weight: 600; color: #3a3a3a; background: transparent; border: none;") + ecc_label.setMinimumWidth(200) + self.eccentricity_input = QLineEdit() + self.eccentricity_input.setFixedWidth(input_width) + apply_field_style(self.eccentricity_input) + ecc_row.addWidget(ecc_label) + ecc_row.addWidget(self.eccentricity_input) + ecc_row.addStretch() + left_layout.addLayout(ecc_row) + + # Footpath Pressure row with dropdown + footpath_row = QHBoxLayout() + footpath_row.setSpacing(10) + footpath_label = QLabel("Footpath Pressure (kN/mm2 ):") + footpath_label.setStyleSheet("font-size: 11px; font-weight: 600; color: #3a3a3a; background: transparent; border: none;") + footpath_label.setMinimumWidth(200) + self.footpath_mode_combo = QComboBox() + self.footpath_mode_combo.addItems(["Automatic", "User-defined"]) + self.footpath_mode_combo.setFixedWidth(input_width) + apply_field_style(self.footpath_mode_combo) + footpath_row.addWidget(footpath_label) + footpath_row.addWidget(self.footpath_mode_combo) + footpath_row.addStretch() + left_layout.addLayout(footpath_row) + + # Value input below footpath (aligned with dropdown above) + value_row = QHBoxLayout() + value_row.setContentsMargins(0, 0, 0, 0) + value_row.setSpacing(10) + value_spacer = QLabel("") + value_spacer.setMinimumWidth(200) + self.footpath_value_input = QLineEdit() + self.footpath_value_input.setPlaceholderText("Value") + self.footpath_value_input.setFixedWidth(input_width) + apply_field_style(self.footpath_value_input) + value_row.addWidget(value_spacer) + value_row.addWidget(self.footpath_value_input) + value_row.addStretch() + left_layout.addLayout(value_row) + + left_layout.addStretch() + + # Right description card + right_card = self._create_card() + right_card.setStyleSheet("QFrame { border: 1px solid #9c9c9c; border-radius: 10px; background-color: #d4d4d4; }") + right_card.setMinimumWidth(260) + right_card.setMinimumHeight(420) + right_layout = QVBoxLayout(right_card) + right_layout.setContentsMargins(16, 16, 16, 16) + right_layout.setSpacing(10) + + # Description Box title - no box around it + desc_label = QLabel("Description Box") + desc_label.setAlignment(Qt.AlignCenter) + desc_label.setStyleSheet("font-size: 12px; font-weight: 700; color: #000000; background: transparent; border: none;") + right_layout.addWidget(desc_label) + + description_text = ( + "211.2 The braking effect on a simply supported span or a continuous unit of spans " + "or on any other type of bridge unit shall be assumed to have the following value:\n\n" + "a) In the case of a single lane or a two lane bridge: twenty percent of the first train " + "load plus ten percent of the load of the succeeding trains or part thereof, the train " + "loads in one lane only being considered for the purpose of this subclause. Where the " + "entire first train is not on the full span, the braking force shall be taken as equal to " + "twenty percent of the loads actually on the span or continuous unit of spans.\n" + "b) In the case of bridges having more than two lanes: as in (a) above for the first two " + "lanes plus five percent of the loads on the lanes in excess of two." + ) + description_label = QLabel(description_text) + description_label.setWordWrap(True) + description_label.setStyleSheet("font-size: 11px; color: #4b4b4b; background: transparent; border: none;") + right_layout.addWidget(description_label) + right_layout.addStretch() + + content_row.addWidget(left_card, 3) + content_row.addWidget(right_card, 2) + page_layout.addLayout(content_row) + page_layout.addSpacing(4) + + self.custom_vehicle_add_button.clicked.connect(self.show_custom_vehicle_dialog) + self.custom_vehicle_edit_button.clicked.connect(self.show_custom_vehicle_dialog) + self.footpath_mode_combo.currentTextChanged.connect(self._on_footpath_mode_changed) + self._on_footpath_mode_changed(self.footpath_mode_combo.currentText()) + + return page + + def _build_seismic_load_tab(self): + """Build the Seismic/Earthquake Load tab matching reference design""" + page = QWidget() + page.setStyleSheet("background-color: #f5f5f5;") + page_layout = QVBoxLayout(page) + page_layout.setContentsMargins(12, 12, 12, 12) + page_layout.setSpacing(12) + + content_row = QHBoxLayout() + content_row.setContentsMargins(0, 0, 0, 0) + content_row.setSpacing(16) + + # Left card with inputs + left_card = self._create_card() + left_card.setStyleSheet("QFrame { border: 1px solid #b2b2b2; border-radius: 10px; background-color: #ffffff; }") + left_layout = QVBoxLayout(left_card) + left_layout.setContentsMargins(16, 16, 16, 16) + left_layout.setSpacing(12) + + # Title + title = QLabel("Seismic/Earthquake Load (EL) Inputs for Evaluation per IRC 6") + title.setStyleSheet("font-size: 12px; font-weight: 700; color: #2b2b2b; background: transparent; border: none;") + left_layout.addWidget(title) + + label_style = "font-size: 11px; color: #3a3a3a; background: transparent; border: none;" + field_width = 120 + + # ===== Seismic Inputs Box ===== + seismic_inputs_box = QFrame() + seismic_inputs_box.setStyleSheet("QFrame { border: 1px solid #b2b2b2; border-radius: 8px; background-color: #ffffff; }") + seismic_inputs_layout = QGridLayout(seismic_inputs_box) + seismic_inputs_layout.setContentsMargins(12, 12, 12, 12) + seismic_inputs_layout.setHorizontalSpacing(12) + seismic_inputs_layout.setVerticalSpacing(10) + seismic_inputs_layout.setColumnMinimumWidth(0, 200) + + row = 0 + + # Seismic Zone + lbl = QLabel("Seismic Zone:") + lbl.setStyleSheet(label_style) + self.seismic_zone_combo = QComboBox() + self.seismic_zone_combo.addItems(["II", "III", "IV", "V"]) + self.seismic_zone_combo.setFixedWidth(field_width) + apply_field_style(self.seismic_zone_combo) + seismic_inputs_layout.addWidget(lbl, row, 0, Qt.AlignLeft | Qt.AlignVCenter) + seismic_inputs_layout.addWidget(self.seismic_zone_combo, row, 1, Qt.AlignLeft) + row += 1 + + # Importance Factor + lbl = QLabel("Importance Factor:") + lbl.setStyleSheet(label_style) + self.importance_factor_input = QLineEdit() + self.importance_factor_input.setText("1") + self.importance_factor_input.setFixedWidth(field_width) + apply_field_style(self.importance_factor_input) + seismic_inputs_layout.addWidget(lbl, row, 0, Qt.AlignLeft | Qt.AlignVCenter) + seismic_inputs_layout.addWidget(self.importance_factor_input, row, 1, Qt.AlignLeft) + row += 1 + + # Type of Soil + lbl = QLabel("Type of Soil:") + lbl.setStyleSheet(label_style) + self.soil_type_combo = QComboBox() + self.soil_type_combo.addItems([ + "Type I – Rocky or Hard Soil Sites (N>30)", + "Type II – Medium Soil Sites", + "Type III – Soft Soil Sites" + ]) + self.soil_type_combo.setFixedWidth(field_width + 30) + apply_field_style(self.soil_type_combo) + seismic_inputs_layout.addWidget(lbl, row, 0, Qt.AlignLeft | Qt.AlignVCenter) + seismic_inputs_layout.addWidget(self.soil_type_combo, row, 1, Qt.AlignLeft) + row += 1 + + # Time Period + lbl = QLabel("Time Period:") + lbl.setStyleSheet(label_style) + self.time_period_input = QLineEdit() + self.time_period_input.setFixedWidth(field_width) + apply_field_style(self.time_period_input) + seismic_inputs_layout.addWidget(lbl, row, 0, Qt.AlignLeft | Qt.AlignVCenter) + seismic_inputs_layout.addWidget(self.time_period_input, row, 1, Qt.AlignLeft) + row += 1 + + # Damping Percentage + lbl = QLabel("Damping Percentage:") + lbl.setStyleSheet(label_style) + self.damping_input = QLineEdit() + self.damping_input.setText("2") + self.damping_input.setFixedWidth(field_width) + apply_field_style(self.damping_input) + seismic_inputs_layout.addWidget(lbl, row, 0, Qt.AlignLeft | Qt.AlignVCenter) + seismic_inputs_layout.addWidget(self.damping_input, row, 1, Qt.AlignLeft) + row += 1 + + # Response Reduction Factor + lbl = QLabel("Response Reduction Factor:") + lbl.setStyleSheet(label_style) + self.response_factor_combo = QComboBox() + self.response_factor_combo.addItems(["1", "2", "3", "4", "5"]) + self.response_factor_combo.setCurrentText("1") + self.response_factor_combo.setFixedWidth(field_width) + apply_field_style(self.response_factor_combo) + seismic_inputs_layout.addWidget(lbl, row, 0, Qt.AlignLeft | Qt.AlignVCenter) + seismic_inputs_layout.addWidget(self.response_factor_combo, row, 1, Qt.AlignLeft) + row += 1 + + # Dead Load for Seismic Force + lbl = QLabel("Dead Load for Seismic Force (kN):") + lbl.setStyleSheet(label_style) + self.dead_load_seismic_combo = QComboBox() + self.dead_load_seismic_combo.addItems(["Automatic", "Custom"]) + self.dead_load_seismic_combo.setFixedWidth(field_width) + apply_field_style(self.dead_load_seismic_combo) + seismic_inputs_layout.addWidget(lbl, row, 0, Qt.AlignLeft | Qt.AlignVCenter) + seismic_inputs_layout.addWidget(self.dead_load_seismic_combo, row, 1, Qt.AlignLeft) + row += 1 + + # Custom Value for Dead Load + self.dead_load_custom_input = QLineEdit() + self.dead_load_custom_input.setPlaceholderText("Custom Value") + self.dead_load_custom_input.setFixedWidth(field_width) + self.dead_load_custom_input.setEnabled(False) + apply_field_style(self.dead_load_custom_input) + seismic_inputs_layout.addWidget(self.dead_load_custom_input, row, 1, Qt.AlignLeft) + row += 1 + + # Live Load for Seismic Force + lbl = QLabel("Live Load for Seismic Force (kN):") + lbl.setStyleSheet(label_style) + self.live_load_seismic_combo = QComboBox() + self.live_load_seismic_combo.addItems(["Automatic", "Custom"]) + self.live_load_seismic_combo.setFixedWidth(field_width) + apply_field_style(self.live_load_seismic_combo) + seismic_inputs_layout.addWidget(lbl, row, 0, Qt.AlignLeft | Qt.AlignVCenter) + seismic_inputs_layout.addWidget(self.live_load_seismic_combo, row, 1, Qt.AlignLeft) + row += 1 + + # Custom Value for Live Load + self.live_load_custom_input = QLineEdit() + self.live_load_custom_input.setPlaceholderText("Custom Value") + self.live_load_custom_input.setFixedWidth(field_width) + self.live_load_custom_input.setEnabled(False) + apply_field_style(self.live_load_custom_input) + seismic_inputs_layout.addWidget(self.live_load_custom_input, row, 1, Qt.AlignLeft) + + left_layout.addWidget(seismic_inputs_box) + + # ===== Computed Values Box ===== + computed_box = QFrame() + computed_box.setStyleSheet("QFrame { border: 1px solid #b2b2b2; border-radius: 8px; background-color: #ffffff; }") + computed_layout = QGridLayout(computed_box) + computed_layout.setContentsMargins(12, 12, 12, 12) + computed_layout.setHorizontalSpacing(12) + computed_layout.setVerticalSpacing(10) + computed_layout.setColumnMinimumWidth(0, 200) + + computed_fields = [ + ("Zone Factor:", "zone_factor"), + ("Spectral Acceleration Coefficient:", "spectral_coeff"), + ("Horizontal Seismic Coefficient:", "horizontal_coeff"), + ("Vertical Seismic Coefficient:", "vertical_coeff"), + ] + + self.seismic_computed_fields = {} + for idx, (label_text, field_name) in enumerate(computed_fields): + lbl = QLabel(label_text) + lbl.setStyleSheet(label_style) + field = QLineEdit() + field.setFixedWidth(field_width) + field.setReadOnly(True) + apply_field_style(field) + computed_layout.addWidget(lbl, idx, 0, Qt.AlignLeft | Qt.AlignVCenter) + computed_layout.addWidget(field, idx, 1, Qt.AlignLeft) + self.seismic_computed_fields[field_name] = field + + left_layout.addWidget(computed_box) + left_layout.addStretch() + + # Right description card + right_card = self._create_card() + right_card.setStyleSheet("QFrame { border: 1px solid #9c9c9c; border-radius: 10px; background-color: #d4d4d4; }") + right_card.setMinimumWidth(200) + right_card.setMinimumHeight(400) + right_layout = QVBoxLayout(right_card) + right_layout.setContentsMargins(16, 16, 16, 16) + right_layout.setSpacing(10) + + desc_title = QLabel("Description Box") + desc_title.setAlignment(Qt.AlignCenter) + desc_title.setStyleSheet("font-size: 12px; font-weight: 700; color: #2b2b2b; background: transparent; border: none;") + right_layout.addWidget(desc_title) + + desc_text = QLabel("Importance factor for normal, important, and critical bridges.") + desc_text.setWordWrap(True) + desc_text.setStyleSheet("font-size: 11px; color: #4b4b4b; background: transparent; border: none;") + right_layout.addWidget(desc_text) + right_layout.addStretch() + + content_row.addWidget(left_card, 3) + content_row.addWidget(right_card, 2) + + page_layout.addLayout(content_row) + + # Connect signals for enabling/disabling custom inputs + self.dead_load_seismic_combo.currentTextChanged.connect(self._on_dead_load_mode_changed) + self.live_load_seismic_combo.currentTextChanged.connect(self._on_live_load_mode_changed) + + return page + + def _on_dead_load_mode_changed(self, mode): + is_custom = mode == "Custom" + self.dead_load_custom_input.setEnabled(is_custom) + if not is_custom: + self.dead_load_custom_input.clear() + + def _on_live_load_mode_changed(self, mode): + is_custom = mode == "Custom" + self.live_load_custom_input.setEnabled(is_custom) + if not is_custom: + self.live_load_custom_input.clear() + + def _add_load_section(self, parent_layout, title, rows): + title_label = QLabel(title) + title_label.setStyleSheet("font-size: 12px; font-weight: 600; color: #3e3e3e; background: transparent; border: none;") + parent_layout.addWidget(title_label) + + grid = QGridLayout() + grid.setContentsMargins(0, 0, 0, 0) + grid.setHorizontalSpacing(12) + grid.setVerticalSpacing(12) + grid.setColumnMinimumWidth(0, 230) + grid.setColumnStretch(0, 0) + grid.setColumnStretch(1, 1) + + field_width = 170 + + for row_index, (label_text, widget) in enumerate(rows): + label = QLabel(label_text) + label.setStyleSheet("font-size: 11px; color: #4b4b4b; background: transparent; border: none;") + grid.addWidget(label, row_index, 0, Qt.AlignLeft | Qt.AlignVCenter) + widget.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) + if isinstance(widget, QComboBox): + widget.setFixedWidth(field_width) + elif isinstance(widget, QLineEdit): + widget.setFixedWidth(field_width) + grid.addWidget(widget, row_index, 1) + + parent_layout.addLayout(grid) + + def _add_checkbox_section(self, parent_layout, title, items): + title_label = QLabel(title) + title_label.setStyleSheet("font-size: 12px; font-weight: 600; color: #3e3e3e; background: transparent; border: none;") + parent_layout.addWidget(title_label) + + grid = QGridLayout() + grid.setContentsMargins(0, 0, 0, 0) + grid.setHorizontalSpacing(12) + grid.setVerticalSpacing(8) + grid.setColumnStretch(0, 1) + + + for row, name in enumerate(items): + label = QLabel(name) + # Make Vehicle Name entries italic + if "Vehicle Name" in name: + label.setStyleSheet("font-size: 11px; font-style: italic; color: #4b4b4b; background: transparent; border: none; padding: 0px;") + else: + label.setStyleSheet("font-size: 11px; color: #4b4b4b; background: transparent; border: none; padding: 0px;") + checkbox = QCheckBox() + checkbox.setChecked(False) + grid.addWidget(label, row, 0, Qt.AlignLeft | Qt.AlignVCenter) + grid.addWidget(checkbox, row, 1, Qt.AlignRight | Qt.AlignVCenter) + + parent_layout.addLayout(grid) + + def _on_footpath_mode_changed(self, mode): + is_custom = mode == "User-defined" + self.footpath_value_input.setEnabled(is_custom) + if not is_custom: + self.footpath_value_input.clear() + + def show_custom_vehicle_dialog(self): + self.custom_vehicle_dialog.show() + self.custom_vehicle_dialog.raise_() + self.custom_vehicle_dialog.activateWindow() + + def _create_yes_no_combo(self): + combo = QComboBox() + combo.addItems(VALUES_YES_NO) + combo.setCurrentText("Yes") + apply_field_style(combo) + return combo + + def _create_line_edit(self): + line_edit = QLineEdit() + apply_field_style(line_edit) + return line_edit + + def _create_card(self): + card = QFrame() + card.setStyleSheet("QFrame { border: 1px solid #cfcfcf; border-radius: 12px; background-color: #ffffff; }") + return card + + def _build_wind_load_tab(self): + """Build the Wind Load tab matching reference design""" + page = QWidget() + page.setStyleSheet("background-color: #f5f5f5;") + page_layout = QVBoxLayout(page) + page_layout.setContentsMargins(12, 12, 12, 12) + page_layout.setSpacing(12) + + content_row = QHBoxLayout() + content_row.setContentsMargins(0, 0, 0, 0) + content_row.setSpacing(16) + + # Left card with inputs - use scroll area for many fields + left_card = self._create_card() + left_card.setStyleSheet("QFrame { border: 1px solid #b2b2b2; border-radius: 10px; background-color: #ffffff; }") + left_card_layout = QVBoxLayout(left_card) + left_card_layout.setContentsMargins(0, 0, 0, 0) + left_card_layout.setSpacing(0) + + scroll = QScrollArea() + scroll.setWidgetResizable(True) + scroll.setFrameShape(QFrame.NoFrame) + scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + scroll.setStyleSheet( + "QScrollArea { border: none; background: transparent; }" + "QScrollArea > QWidget > QWidget { background: transparent; }" + ) + + scroll_content = QWidget() + scroll_content.setStyleSheet("background: #ffffff;") + left_layout = QVBoxLayout(scroll_content) + left_layout.setContentsMargins(16, 16, 16, 16) + left_layout.setSpacing(12) + + label_style = "font-size: 11px; color: #3a3a3a; background: transparent; border: none;" + field_width = 120 + + # ===== Wind Load Inputs Box ===== + wind_inputs_box = QFrame() + wind_inputs_box.setStyleSheet("QFrame { border: 1px solid #b2b2b2; border-radius: 8px; background-color: #ffffff; }") + wind_inputs_layout = QVBoxLayout(wind_inputs_box) + wind_inputs_layout.setContentsMargins(12, 12, 12, 12) + wind_inputs_layout.setSpacing(10) + + wind_title = QLabel("Wind Load (WL) Inputs for Evaluation per IRC6") + wind_title.setStyleSheet("font-size: 12px; font-weight: 700; color: #2b2b2b; background: transparent; border: none;") + wind_inputs_layout.addWidget(wind_title) + + wind_grid = QGridLayout() + wind_grid.setContentsMargins(0, 4, 0, 0) + wind_grid.setHorizontalSpacing(12) + wind_grid.setVerticalSpacing(8) + wind_grid.setColumnMinimumWidth(0, 220) + + row = 0 + + # Basic Wind Speed + lbl = QLabel("Basic Wind Speed (m/s):") + lbl.setStyleSheet(label_style) + self.basic_wind_speed_input = QLineEdit() + self.basic_wind_speed_input.setFixedWidth(field_width) + apply_field_style(self.basic_wind_speed_input) + wind_grid.addWidget(lbl, row, 0, Qt.AlignLeft | Qt.AlignVCenter) + wind_grid.addWidget(self.basic_wind_speed_input, row, 1, Qt.AlignLeft) + row += 1 + + # Average Exposed Height + lbl = QLabel("Average Exposed Height (m):") + lbl.setStyleSheet(label_style) + self.avg_exposed_height_input = QLineEdit() + self.avg_exposed_height_input.setFixedWidth(field_width) + apply_field_style(self.avg_exposed_height_input) + wind_grid.addWidget(lbl, row, 0, Qt.AlignLeft | Qt.AlignVCenter) + wind_grid.addWidget(self.avg_exposed_height_input, row, 1, Qt.AlignLeft) + row += 1 + + # Type of Terrain + lbl = QLabel("Type of Terrain:") + lbl.setStyleSheet(label_style) + self.terrain_type_combo = QComboBox() + self.terrain_type_combo.addItems(["Plain", "Hilly", "Coastal"]) + self.terrain_type_combo.setFixedWidth(field_width) + apply_field_style(self.terrain_type_combo) + wind_grid.addWidget(lbl, row, 0, Qt.AlignLeft | Qt.AlignVCenter) + wind_grid.addWidget(self.terrain_type_combo, row, 1, Qt.AlignLeft) + row += 1 + + # Site Topography + lbl = QLabel("Site Topography:") + lbl.setStyleSheet(label_style) + self.site_topography_combo = QComboBox() + self.site_topography_combo.addItems(["Flat", "Hilly", "Ridge", "Valley"]) + self.site_topography_combo.setFixedWidth(field_width) + apply_field_style(self.site_topography_combo) + wind_grid.addWidget(lbl, row, 0, Qt.AlignLeft | Qt.AlignVCenter) + wind_grid.addWidget(self.site_topography_combo, row, 1, Qt.AlignLeft) + row += 1 + + # Gust Factor, G + lbl = QLabel("Gust Factor, G:") + lbl.setStyleSheet(label_style) + self.gust_factor_combo = QComboBox() + self.gust_factor_combo.addItems(["Automatic", "Custom"]) + self.gust_factor_combo.setFixedWidth(field_width) + apply_field_style(self.gust_factor_combo) + wind_grid.addWidget(lbl, row, 0, Qt.AlignLeft | Qt.AlignVCenter) + wind_grid.addWidget(self.gust_factor_combo, row, 1, Qt.AlignLeft) + row += 1 + self.gust_factor_value = QLineEdit() + self.gust_factor_value.setPlaceholderText("Value") + self.gust_factor_value.setFixedWidth(field_width) + self.gust_factor_value.setEnabled(False) + apply_field_style(self.gust_factor_value) + wind_grid.addWidget(self.gust_factor_value, row, 1, Qt.AlignLeft) + row += 1 + + # Drag Coefficient, CD + lbl = QLabel("Drag Coefficient, CD:") + lbl.setStyleSheet(label_style) + self.drag_coeff_combo = QComboBox() + self.drag_coeff_combo.addItems(["Automatic", "Custom"]) + self.drag_coeff_combo.setFixedWidth(field_width) + apply_field_style(self.drag_coeff_combo) + wind_grid.addWidget(lbl, row, 0, Qt.AlignLeft | Qt.AlignVCenter) + wind_grid.addWidget(self.drag_coeff_combo, row, 1, Qt.AlignLeft) + row += 1 + self.drag_coeff_value = QLineEdit() + self.drag_coeff_value.setPlaceholderText("Custom Value") + self.drag_coeff_value.setFixedWidth(field_width) + self.drag_coeff_value.setEnabled(False) + apply_field_style(self.drag_coeff_value) + wind_grid.addWidget(self.drag_coeff_value, row, 1, Qt.AlignLeft) + row += 1 + + # Drag Coefficient against Live Load, CDLL + lbl = QLabel("Drag Coefficient against Live Load, CDLL:") + lbl.setStyleSheet(label_style) + self.drag_coeff_ll_combo = QComboBox() + self.drag_coeff_ll_combo.addItems(["Automatic", "Custom"]) + self.drag_coeff_ll_combo.setFixedWidth(field_width) + apply_field_style(self.drag_coeff_ll_combo) + wind_grid.addWidget(lbl, row, 0, Qt.AlignLeft | Qt.AlignVCenter) + wind_grid.addWidget(self.drag_coeff_ll_combo, row, 1, Qt.AlignLeft) + row += 1 + self.drag_coeff_ll_value = QLineEdit() + self.drag_coeff_ll_value.setPlaceholderText("Value") + self.drag_coeff_ll_value.setFixedWidth(field_width) + self.drag_coeff_ll_value.setEnabled(False) + apply_field_style(self.drag_coeff_ll_value) + wind_grid.addWidget(self.drag_coeff_ll_value, row, 1, Qt.AlignLeft) + row += 1 + + # Lift Coefficient, CL + lbl = QLabel("Lift Coefficient, CL:") + lbl.setStyleSheet(label_style) + self.lift_coeff_combo = QComboBox() + self.lift_coeff_combo.addItems(["Automatic", "Custom"]) + self.lift_coeff_combo.setFixedWidth(field_width) + apply_field_style(self.lift_coeff_combo) + wind_grid.addWidget(lbl, row, 0, Qt.AlignLeft | Qt.AlignVCenter) + wind_grid.addWidget(self.lift_coeff_combo, row, 1, Qt.AlignLeft) + row += 1 + self.lift_coeff_value = QLineEdit() + self.lift_coeff_value.setPlaceholderText("Value") + self.lift_coeff_value.setFixedWidth(field_width) + self.lift_coeff_value.setEnabled(False) + apply_field_style(self.lift_coeff_value) + wind_grid.addWidget(self.lift_coeff_value, row, 1, Qt.AlignLeft) + row += 1 + + # Superstructure Area in Elevation + lbl = QLabel("Superstructure Area in Elevation (m2):") + lbl.setStyleSheet(label_style) + self.super_area_elev_combo = QComboBox() + self.super_area_elev_combo.addItems(["Automatic", "Custom"]) + self.super_area_elev_combo.setFixedWidth(field_width) + apply_field_style(self.super_area_elev_combo) + wind_grid.addWidget(lbl, row, 0, Qt.AlignLeft | Qt.AlignVCenter) + wind_grid.addWidget(self.super_area_elev_combo, row, 1, Qt.AlignLeft) + row += 1 + self.super_area_elev_value = QLineEdit() + self.super_area_elev_value.setPlaceholderText("Custom Value") + self.super_area_elev_value.setFixedWidth(field_width) + self.super_area_elev_value.setEnabled(False) + apply_field_style(self.super_area_elev_value) + wind_grid.addWidget(self.super_area_elev_value, row, 1, Qt.AlignLeft) + row += 1 + + # Superstructure Area in Plain + lbl = QLabel("Superstructure Area in Plain (m2):") + lbl.setStyleSheet(label_style) + self.super_area_plain_combo = QComboBox() + self.super_area_plain_combo.addItems(["Automatic", "Custom"]) + self.super_area_plain_combo.setFixedWidth(field_width) + apply_field_style(self.super_area_plain_combo) + wind_grid.addWidget(lbl, row, 0, Qt.AlignLeft | Qt.AlignVCenter) + wind_grid.addWidget(self.super_area_plain_combo, row, 1, Qt.AlignLeft) + row += 1 + self.super_area_plain_value = QLineEdit() + self.super_area_plain_value.setPlaceholderText("Custom Value") + self.super_area_plain_value.setFixedWidth(field_width) + self.super_area_plain_value.setEnabled(False) + apply_field_style(self.super_area_plain_value) + wind_grid.addWidget(self.super_area_plain_value, row, 1, Qt.AlignLeft) + row += 1 + + # Exposed Frontal Area of Live Load + lbl = QLabel("Exposed Frontal Area of Live Load (m2):") + lbl.setStyleSheet(label_style) + self.exposed_frontal_area_combo = QComboBox() + self.exposed_frontal_area_combo.addItems(["Automatic", "Custom"]) + self.exposed_frontal_area_combo.setFixedWidth(field_width) + apply_field_style(self.exposed_frontal_area_combo) + wind_grid.addWidget(lbl, row, 0, Qt.AlignLeft | Qt.AlignVCenter) + wind_grid.addWidget(self.exposed_frontal_area_combo, row, 1, Qt.AlignLeft) + row += 1 + self.exposed_frontal_area_value = QLineEdit() + self.exposed_frontal_area_value.setPlaceholderText("Custom Value") + self.exposed_frontal_area_value.setFixedWidth(field_width) + self.exposed_frontal_area_value.setEnabled(False) + apply_field_style(self.exposed_frontal_area_value) + wind_grid.addWidget(self.exposed_frontal_area_value, row, 1, Qt.AlignLeft) + row += 1 + + # Wind Load Eccentricity from Top of Deck + lbl = QLabel("Wind Load Eccentricity from Top of Deck\n(m): Negative for below deck") + lbl.setStyleSheet(label_style) + self.wind_ecc_deck_combo = QComboBox() + self.wind_ecc_deck_combo.addItems(["Automatic", "Custom"]) + self.wind_ecc_deck_combo.setFixedWidth(field_width) + apply_field_style(self.wind_ecc_deck_combo) + wind_grid.addWidget(lbl, row, 0, Qt.AlignLeft | Qt.AlignVCenter) + wind_grid.addWidget(self.wind_ecc_deck_combo, row, 1, Qt.AlignLeft) + row += 1 + self.wind_ecc_deck_value = QLineEdit() + self.wind_ecc_deck_value.setPlaceholderText("Value") + self.wind_ecc_deck_value.setFixedWidth(field_width) + self.wind_ecc_deck_value.setEnabled(False) + apply_field_style(self.wind_ecc_deck_value) + wind_grid.addWidget(self.wind_ecc_deck_value, row, 1, Qt.AlignLeft) + row += 1 + + # Wind on Live Load Eccentricity from Top of Deck + lbl = QLabel("Wind on Live Load Eccentricity from Top\nof Deck (m):") + lbl.setStyleSheet(label_style) + self.wind_ll_ecc_combo = QComboBox() + self.wind_ll_ecc_combo.addItems(["Automatic", "Custom"]) + self.wind_ll_ecc_combo.setFixedWidth(field_width) + apply_field_style(self.wind_ll_ecc_combo) + wind_grid.addWidget(lbl, row, 0, Qt.AlignLeft | Qt.AlignVCenter) + wind_grid.addWidget(self.wind_ll_ecc_combo, row, 1, Qt.AlignLeft) + row += 1 + self.wind_ll_ecc_value = QLineEdit() + self.wind_ll_ecc_value.setPlaceholderText("Value") + self.wind_ll_ecc_value.setFixedWidth(field_width) + self.wind_ll_ecc_value.setEnabled(False) + apply_field_style(self.wind_ll_ecc_value) + wind_grid.addWidget(self.wind_ll_ecc_value, row, 1, Qt.AlignLeft) + + wind_inputs_layout.addLayout(wind_grid) + left_layout.addWidget(wind_inputs_box) + + # ===== Computed Values Box ===== + computed_box = QFrame() + computed_box.setStyleSheet("QFrame { border: 1px solid #b2b2b2; border-radius: 8px; background-color: #ffffff; }") + computed_layout = QGridLayout(computed_box) + computed_layout.setContentsMargins(12, 12, 12, 12) + computed_layout.setHorizontalSpacing(12) + computed_layout.setVerticalSpacing(8) + computed_layout.setColumnMinimumWidth(0, 220) + + computed_fields = [ + ("Hourly Mean Wind Speed (m/s):", "hourly_mean_wind"), + ("Hourly Wind Pressure in N/m2:", "hourly_wind_pressure"), + ("Transverse Wind Force in N:", "transverse_wind_force"), + ("Longitudinal Wind Force in N:", "longitudinal_wind_force"), + ("Vertical Wind Force in N:", "vertical_wind_force"), + ("Transverse Wind Force on Live\nLoad in N:", "transverse_wind_ll"), + ("Longitudinal Wind Force on Live\nLoad in N:", "longitudinal_wind_ll"), + ] + + self.wind_computed_fields = {} + for idx, (label_text, field_name) in enumerate(computed_fields): + lbl = QLabel(label_text) + lbl.setStyleSheet(label_style) + field = QLineEdit() + field.setFixedWidth(field_width) + field.setReadOnly(True) + apply_field_style(field) + computed_layout.addWidget(lbl, idx, 0, Qt.AlignLeft | Qt.AlignVCenter) + computed_layout.addWidget(field, idx, 1, Qt.AlignLeft) + self.wind_computed_fields[field_name] = field + + left_layout.addWidget(computed_box) + left_layout.addStretch() + + scroll.setWidget(scroll_content) + left_card_layout.addWidget(scroll) + + # Right description card + right_card = self._create_card() + right_card.setStyleSheet("QFrame { border: 1px solid #9c9c9c; border-radius: 10px; background-color: #d4d4d4; }") + right_card.setMinimumWidth(150) + right_card.setMaximumWidth(200) + right_layout = QVBoxLayout(right_card) + right_layout.setContentsMargins(16, 16, 16, 16) + right_layout.setSpacing(10) + + desc_title = QLabel("Description\nBox") + desc_title.setAlignment(Qt.AlignCenter) + desc_title.setStyleSheet("font-size: 12px; font-weight: 700; color: #2b2b2b; background: transparent; border: none;") + right_layout.addWidget(desc_title) + right_layout.addStretch() + + content_row.addWidget(left_card, 3) + content_row.addWidget(right_card, 1) + + page_layout.addLayout(content_row) + + # Connect signals for enabling/disabling custom inputs + self.gust_factor_combo.currentTextChanged.connect(lambda t: self.gust_factor_value.setEnabled(t == "Custom")) + self.drag_coeff_combo.currentTextChanged.connect(lambda t: self.drag_coeff_value.setEnabled(t == "Custom")) + self.drag_coeff_ll_combo.currentTextChanged.connect(lambda t: self.drag_coeff_ll_value.setEnabled(t == "Custom")) + self.lift_coeff_combo.currentTextChanged.connect(lambda t: self.lift_coeff_value.setEnabled(t == "Custom")) + self.super_area_elev_combo.currentTextChanged.connect(lambda t: self.super_area_elev_value.setEnabled(t == "Custom")) + self.super_area_plain_combo.currentTextChanged.connect(lambda t: self.super_area_plain_value.setEnabled(t == "Custom")) + self.exposed_frontal_area_combo.currentTextChanged.connect(lambda t: self.exposed_frontal_area_value.setEnabled(t == "Custom")) + self.wind_ecc_deck_combo.currentTextChanged.connect(lambda t: self.wind_ecc_deck_value.setEnabled(t == "Custom")) + self.wind_ll_ecc_combo.currentTextChanged.connect(lambda t: self.wind_ll_ecc_value.setEnabled(t == "Custom")) + + return page + + def _build_temperature_load_tab(self): + """Build the Temperature Load tab matching reference design""" + page = QWidget() + page.setStyleSheet("background-color: #f5f5f5;") + page_layout = QVBoxLayout(page) + page_layout.setContentsMargins(12, 12, 12, 12) + page_layout.setSpacing(12) + + content_row = QHBoxLayout() + content_row.setContentsMargins(0, 0, 0, 0) + content_row.setSpacing(16) + + # Left card with inputs + left_card = self._create_card() + left_card.setStyleSheet("QFrame { border: 1px solid #b2b2b2; border-radius: 10px; background-color: #ffffff; }") + left_layout = QVBoxLayout(left_card) + left_layout.setContentsMargins(16, 16, 16, 16) + left_layout.setSpacing(12) + + label_style = "font-size: 11px; color: #3a3a3a; background: transparent; border: none;" + field_width = 120 + + # ===== Inputs for evaluation per IRC6 Box ===== + irc6_box = QFrame() + irc6_box.setStyleSheet("QFrame { border: 1px solid #b2b2b2; border-radius: 8px; background-color: #ffffff; }") + irc6_layout = QVBoxLayout(irc6_box) + irc6_layout.setContentsMargins(12, 12, 12, 12) + irc6_layout.setSpacing(10) + + irc6_title = QLabel("Inputs for evaluation per IRC6") + irc6_title.setStyleSheet("font-size: 12px; font-weight: 700; color: #2b2b2b; background: transparent; border: none;") + irc6_layout.addWidget(irc6_title) + + irc6_grid = QGridLayout() + irc6_grid.setContentsMargins(0, 4, 0, 0) + irc6_grid.setHorizontalSpacing(12) + irc6_grid.setVerticalSpacing(10) + irc6_grid.setColumnMinimumWidth(0, 200) + + # Highest Maximum Air Temperature + lbl = QLabel("Highest Maximum Air Temperature:") + lbl.setStyleSheet(label_style) + self.highest_max_temp_input = QLineEdit() + self.highest_max_temp_input.setFixedWidth(field_width) + apply_field_style(self.highest_max_temp_input) + irc6_grid.addWidget(lbl, 0, 0, Qt.AlignLeft | Qt.AlignVCenter) + irc6_grid.addWidget(self.highest_max_temp_input, 0, 1, Qt.AlignLeft) + + # Lowest Minimum Air Temperature + lbl = QLabel("Lowest Minimum Air Temperature:") + lbl.setStyleSheet(label_style) + self.lowest_min_temp_input = QLineEdit() + self.lowest_min_temp_input.setFixedWidth(field_width) + apply_field_style(self.lowest_min_temp_input) + irc6_grid.addWidget(lbl, 1, 0, Qt.AlignLeft | Qt.AlignVCenter) + irc6_grid.addWidget(self.lowest_min_temp_input, 1, 1, Qt.AlignLeft) + + irc6_layout.addLayout(irc6_grid) + left_layout.addWidget(irc6_box) + + # ===== Range of Effective Bridge Temperature Box ===== + range_box = QFrame() + range_box.setStyleSheet("QFrame { border: 1px solid #b2b2b2; border-radius: 8px; background-color: #ffffff; }") + range_layout = QVBoxLayout(range_box) + range_layout.setContentsMargins(12, 12, 12, 12) + range_layout.setSpacing(10) + + range_title = QLabel("Range of Effective Bridge Temperature:") + range_title.setStyleSheet("font-size: 12px; font-weight: 700; color: #2b2b2b; background: transparent; border: none;") + range_layout.addWidget(range_title) + + range_grid = QGridLayout() + range_grid.setContentsMargins(0, 4, 0, 0) + range_grid.setHorizontalSpacing(12) + range_grid.setVerticalSpacing(10) + range_grid.setColumnMinimumWidth(0, 200) + + # Minimum + lbl = QLabel("Minimum:") + lbl.setStyleSheet(label_style) + self.bridge_temp_min_input = QLineEdit() + self.bridge_temp_min_input.setFixedWidth(field_width) + apply_field_style(self.bridge_temp_min_input) + range_grid.addWidget(lbl, 0, 0, Qt.AlignLeft | Qt.AlignVCenter) + range_grid.addWidget(self.bridge_temp_min_input, 0, 1, Qt.AlignLeft) + + # Maximum + lbl = QLabel("Maximum:") + lbl.setStyleSheet(label_style) + self.bridge_temp_max_input = QLineEdit() + self.bridge_temp_max_input.setFixedWidth(field_width) + apply_field_style(self.bridge_temp_max_input) + range_grid.addWidget(lbl, 1, 0, Qt.AlignLeft | Qt.AlignVCenter) + range_grid.addWidget(self.bridge_temp_max_input, 1, 1, Qt.AlignLeft) + + range_layout.addLayout(range_grid) + left_layout.addWidget(range_box) + + # ===== Coefficient of Thermal Expansion Box ===== + coeff_box = QFrame() + coeff_box.setStyleSheet("QFrame { border: 1px solid #b2b2b2; border-radius: 8px; background-color: #ffffff; }") + coeff_layout = QGridLayout(coeff_box) + coeff_layout.setContentsMargins(12, 12, 12, 12) + coeff_layout.setHorizontalSpacing(12) + coeff_layout.setVerticalSpacing(10) + coeff_layout.setColumnMinimumWidth(0, 200) + + lbl = QLabel("Coefficient of Thermal Expansion for Steel:") + lbl.setStyleSheet(label_style) + self.thermal_coeff_combo = QComboBox() + self.thermal_coeff_combo.addItems(["12 × 10⁻⁶ /°C", "11.7 × 10⁻⁶ /°C", "Custom"]) + self.thermal_coeff_combo.setFixedWidth(field_width) + apply_field_style(self.thermal_coeff_combo) + coeff_layout.addWidget(lbl, 0, 0, Qt.AlignLeft | Qt.AlignVCenter) + coeff_layout.addWidget(self.thermal_coeff_combo, 0, 1, Qt.AlignLeft) + + left_layout.addWidget(coeff_box) + left_layout.addStretch() + + # Right description card + right_card = self._create_card() + right_card.setStyleSheet("QFrame { border: 1px solid #9c9c9c; border-radius: 10px; background-color: #d4d4d4; }") + right_card.setMinimumWidth(200) + right_card.setMinimumHeight(400) + right_layout = QVBoxLayout(right_card) + right_layout.setContentsMargins(16, 16, 16, 16) + right_layout.setSpacing(10) + + desc_title = QLabel("Description Box") + desc_title.setAlignment(Qt.AlignCenter) + desc_title.setStyleSheet("font-size: 12px; font-weight: 700; color: #2b2b2b; background: transparent; border: none;") + right_layout.addWidget(desc_title) + right_layout.addStretch() + + content_row.addWidget(left_card, 3) + content_row.addWidget(right_card, 2) + + page_layout.addLayout(content_row) + + return page + + def _build_load_combination_tab(self): + """Build the Load Combination tab matching reference design""" + page = QWidget() + page.setStyleSheet("background-color: #f5f5f5;") + page_layout = QVBoxLayout(page) + page_layout.setContentsMargins(12, 12, 12, 12) + page_layout.setSpacing(12) + + content_row = QHBoxLayout() + content_row.setContentsMargins(0, 0, 0, 0) + content_row.setSpacing(16) + + label_style = "font-size: 11px; color: #3a3a3a; background: transparent; border: none;" + + # Left card - combination list + left_card = self._create_card() + left_card.setStyleSheet("QFrame { border: 1px solid #b2b2b2; border-radius: 10px; background-color: #ffffff; }") + left_layout = QVBoxLayout(left_card) + left_layout.setContentsMargins(16, 16, 16, 16) + left_layout.setSpacing(12) + + # Auto include checkbox row + auto_row = QHBoxLayout() + auto_row.setContentsMargins(0, 0, 0, 0) + auto_row.setSpacing(8) + auto_label = QLabel("Auto include all IRC 6 Load Combinations") + auto_label.setStyleSheet("font-size: 11px; color: #3a3a3a; background: transparent; border: none;") + self.auto_include_checkbox = QCheckBox() + auto_row.addWidget(auto_label) + auto_row.addWidget(self.auto_include_checkbox) + auto_row.addStretch() + left_layout.addLayout(auto_row) + + # Combination Name label + combo_name_label = QLabel("Combination Name") + combo_name_label.setStyleSheet("font-size: 12px; font-weight: 700; color: #2b2b2b; background: transparent; border: none;") + left_layout.addWidget(combo_name_label) + + # Combination list area + self.combination_list_widget = QWidget() + self.combination_list_widget.setStyleSheet("background: #ffffff;") + self.combination_list_layout = QVBoxLayout(self.combination_list_widget) + self.combination_list_layout.setContentsMargins(0, 8, 0, 8) + self.combination_list_layout.setSpacing(8) + + # Add sample combinations + sample_combos = ["DL + LL", "1.35 DL + 1.75 LL"] + for combo_text in sample_combos: + combo_label = QLabel(combo_text) + combo_label.setStyleSheet(label_style) + self.combination_list_layout.addWidget(combo_label) + + self.combination_list_layout.addStretch() + left_layout.addWidget(self.combination_list_widget, 1) + + # Middle card - combination editor + middle_card = self._create_card() + middle_card.setStyleSheet("QFrame { border: 1px solid #b2b2b2; border-radius: 10px; background-color: #ffffff; }") + middle_layout = QVBoxLayout(middle_card) + middle_layout.setContentsMargins(16, 16, 16, 16) + middle_layout.setSpacing(12) + + # Combination Name title + combo_title = QLabel("Combination Name") + combo_title.setStyleSheet("font-size: 12px; font-weight: 700; color: #2b2b2b; background: transparent; border: none;") + middle_layout.addWidget(combo_title) + + # Editor area with table and buttons + editor_row = QHBoxLayout() + editor_row.setContentsMargins(0, 0, 0, 0) + editor_row.setSpacing(12) + + # Table for Load Name and Scale Factor + table_box = QFrame() + table_box.setStyleSheet("QFrame { border: 1px solid #b2b2b2; border-radius: 4px; background-color: #ffffff; }") + table_layout = QVBoxLayout(table_box) + table_layout.setContentsMargins(0, 0, 0, 0) + table_layout.setSpacing(0) + + # Header row + header_widget = QWidget() + header_widget.setStyleSheet("background: #ffffff; border-bottom: 1px solid #b2b2b2;") + header_layout = QHBoxLayout(header_widget) + header_layout.setContentsMargins(8, 8, 8, 8) + header_layout.setSpacing(8) + + load_name_header = QLabel("Load Name") + load_name_header.setStyleSheet("font-size: 11px; font-weight: 600; color: #3a3a3a; background: transparent; border: none;") + load_name_header.setMinimumWidth(80) + scale_factor_header = QLabel("Scale Factor") + scale_factor_header.setStyleSheet("font-size: 11px; font-weight: 600; color: #3a3a3a; background: transparent; border: none;") + scale_factor_header.setMinimumWidth(80) + + header_layout.addWidget(load_name_header) + header_layout.addWidget(scale_factor_header) + table_layout.addWidget(header_widget) + + # Input row + input_widget = QWidget() + input_widget.setStyleSheet("background: #ffffff;") + input_layout = QHBoxLayout(input_widget) + input_layout.setContentsMargins(8, 8, 8, 8) + input_layout.setSpacing(8) + + self.load_name_combo = QComboBox() + self.load_name_combo.addItems(["DL", "LL", "WL", "EL", "TL"]) + self.load_name_combo.setMinimumWidth(80) + apply_field_style(self.load_name_combo) + + self.scale_factor_input = QLineEdit() + self.scale_factor_input.setMinimumWidth(80) + apply_field_style(self.scale_factor_input) + + input_layout.addWidget(self.load_name_combo) + input_layout.addWidget(self.scale_factor_input) + table_layout.addWidget(input_widget) + + # Empty space for more rows + table_layout.addStretch() + + editor_row.addWidget(table_box, 1) + + # Add/Delete buttons column + button_col = QVBoxLayout() + button_col.setContentsMargins(0, 0, 0, 0) + button_col.setSpacing(8) + + self.add_load_btn = QPushButton("Add") + self.add_load_btn.setFixedWidth(60) + self.add_load_btn.setStyleSheet( + "QPushButton { background: #ffffff; border: 1px solid #b2b2b2; border-radius: 4px; padding: 6px 12px; font-size: 11px; color: #3a3a3a; }" + "QPushButton:hover { background: #f0f0f0; }" + "QPushButton:pressed { background: #e0e0e0; }" + ) + + self.delete_load_btn = QPushButton("Delete") + self.delete_load_btn.setFixedWidth(60) + self.delete_load_btn.setStyleSheet( + "QPushButton { background: #ffffff; border: 1px solid #b2b2b2; border-radius: 4px; padding: 6px 12px; font-size: 11px; color: #3a3a3a; }" + "QPushButton:hover { background: #f0f0f0; }" + "QPushButton:pressed { background: #e0e0e0; }" + ) + + button_col.addWidget(self.add_load_btn) + button_col.addWidget(self.delete_load_btn) + button_col.addStretch() + + editor_row.addLayout(button_col) + middle_layout.addLayout(editor_row, 1) + + content_row.addWidget(left_card, 2) + content_row.addWidget(middle_card, 3) + + page_layout.addLayout(content_row) + + return page + + def _create_placeholder_page(self, title): + page = QWidget() + page.setStyleSheet("background-color: #f5f5f5;") + layout = QVBoxLayout(page) + layout.setAlignment(Qt.AlignCenter) + layout.setContentsMargins(20, 20, 20, 20) + + label = QLabel(f"{title} inputs will be added soon.") + label.setAlignment(Qt.AlignCenter) + label.setStyleSheet("font-size: 12px; color: #6a6a6a;") + layout.addWidget(label) + return page + + +class StretchingTabBar(QTabBar): + """Tab bar that distributes tab widths across available space.""" + + def tabSizeHint(self, index): + hint = super().tabSizeHint(index) + count = max(1, self.count()) + available_width = max(self.width(), hint.width() * count) + stretched_width = max(hint.width(), available_width // count) + return QSize(stretched_width, hint.height()) + + def minimumTabSizeHint(self, index): + return self.tabSizeHint(index) + + def resizeEvent(self, event): + super().resizeEvent(event) + self.updateGeometry() + + +class AdditionalInputs(QWidget): + """Main widget for Additional Inputs with tabbed interface""" + + def __init__(self, footpath_value="None", carriageway_width=7.5, parent=None): + super().__init__(parent) + self.setObjectName("AdditionalInputs") + self.footpath_value = footpath_value + self.carriageway_width = carriageway_width + self.init_ui() + + def init_ui(self): + # Set explicit white background to prevent black background issue + self.setStyleSheet("QWidget#AdditionalInputs { background-color: #ffffff; }") + self.setAttribute(Qt.WA_StyledBackground, True) + + main_layout = QVBoxLayout(self) + main_layout.setContentsMargins(5, 5, 5, 5) + + # Main tab widget + self.tabs = QTabWidget() + self.tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) + self.stretching_tab_bar = StretchingTabBar() + self.stretching_tab_bar.setElideMode(Qt.ElideRight) + self.tabs.setTabBar(self.stretching_tab_bar) + self.tabs.setStyleSheet(""" + QTabWidget::pane { + border: 1px solid #d1d1d1; + background-color: #ffffff; + border-radius: 6px; + } + QTabBar::tab { + font-weight: bold; + font-size: 12px; + background: #ffffff; + color: #3a3a3a; + border: 1px solid #d1d1d1; + padding: 10px 22px; + } + QTabBar::tab:selected { + background: #90AF13; + color: #ffffff; + border: 1px solid #90AF13; + } + QTabBar::tab:hover { + background: #90AF13; + color: #ffffff; + } + """) + + # Sub-Tab 1: Typical Section Details + self.typical_section_tab = TypicalSectionDetailsTab(self.footpath_value, self.carriageway_width) + self.tabs.addTab(self.typical_section_tab, "Typical Section Details") + + # Sub-Tab 2: Member Properties + self.section_properties_tab = SectionPropertiesTab() + self.tabs.addTab(self.section_properties_tab, "Member Properties") + + # Sub-Tab 3: Loading + self.loading_tab = LoadingTab() + self.tabs.addTab(self.loading_tab, "Loading") + + # Sub-Tab 4: Support Conditions + support_tab = self._build_support_conditions_tab() + self.tabs.addTab(support_tab, "Support Conditions") + + # Sub-Tab 5: Design Options + design_options_tab = self._build_design_options_tab() + self.tabs.addTab(design_options_tab, "Design Options") + + # Sub-Tab 6: Design Options (Cont.) + analysis_design_tab = self.create_placeholder_tab( + "Design Options (Cont.)", + "This tab will contain:\n\n" + + "• Analysis Method\n" + + "• Design Code Options\n" + + "• Safety Factors\n" + + "• Other Design Parameters\n\n" + + "Implementation in progress..." + ) + self.tabs.addTab(analysis_design_tab, "Design Options (Cont.)") + + main_layout.addWidget(self.tabs) + + def _build_support_conditions_tab(self): + """Build the Support Conditions tab matching reference design""" + widget = QWidget() + widget.setStyleSheet("background-color: #f5f5f5;") + + main_layout = QVBoxLayout(widget) + main_layout.setContentsMargins(12, 12, 12, 12) + main_layout.setSpacing(12) + + # Main card + card = QFrame() + card.setStyleSheet("QFrame { border: 1px solid #b2b2b2; border-radius: 10px; background-color: #ffffff; }") + card_layout = QVBoxLayout(card) + card_layout.setContentsMargins(16, 16, 16, 16) + card_layout.setSpacing(16) + + label_style = "font-size: 11px; color: #3a3a3a; background: transparent; border: none;" + heading_style = "font-size: 12px; font-weight: 700; color: #2b2b2b; background: transparent; border: none;" + field_width = 120 + + # Support Condition section + support_title = QLabel("Support Condition*") + support_title.setStyleSheet(heading_style) + card_layout.addWidget(support_title) + + support_grid = QGridLayout() + support_grid.setContentsMargins(0, 8, 0, 0) + support_grid.setHorizontalSpacing(12) + support_grid.setVerticalSpacing(12) + support_grid.setColumnMinimumWidth(0, 120) + + # Left Support + lbl = QLabel("Left Support:") + lbl.setStyleSheet(label_style) + self.left_support_combo = QComboBox() + self.left_support_combo.addItems(["Fixed", "Pinned", "Roller"]) + self.left_support_combo.setFixedWidth(field_width) + apply_field_style(self.left_support_combo) + support_grid.addWidget(lbl, 0, 0, Qt.AlignLeft | Qt.AlignVCenter) + support_grid.addWidget(self.left_support_combo, 0, 1, Qt.AlignLeft) + + # Right Support + lbl = QLabel("Right Support:") + lbl.setStyleSheet(label_style) + self.right_support_combo = QComboBox() + self.right_support_combo.addItems(["Fixed", "Pinned", "Roller"]) + self.right_support_combo.setFixedWidth(field_width) + apply_field_style(self.right_support_combo) + support_grid.addWidget(lbl, 1, 0, Qt.AlignLeft | Qt.AlignVCenter) + support_grid.addWidget(self.right_support_combo, 1, 1, Qt.AlignLeft) + + card_layout.addLayout(support_grid) + + # Bearing Length section + bearing_title = QLabel("Bearing length*") + bearing_title.setStyleSheet(heading_style) + card_layout.addWidget(bearing_title) + + bearing_grid = QGridLayout() + bearing_grid.setContentsMargins(0, 8, 0, 0) + bearing_grid.setHorizontalSpacing(12) + bearing_grid.setVerticalSpacing(12) + bearing_grid.setColumnMinimumWidth(0, 120) + + lbl = QLabel("Bearing Length Value") + lbl.setStyleSheet(label_style) + self.bearing_length_input = QLineEdit() + self.bearing_length_input.setText("0") + self.bearing_length_input.setFixedWidth(field_width) + apply_field_style(self.bearing_length_input) + bearing_grid.addWidget(lbl, 0, 0, Qt.AlignLeft | Qt.AlignVCenter) + bearing_grid.addWidget(self.bearing_length_input, 0, 1, Qt.AlignLeft) + + card_layout.addLayout(bearing_grid) + card_layout.addStretch() + + main_layout.addWidget(card) + main_layout.addStretch() + + return widget + + def _build_design_options_tab(self): + """Build the Design Options tab matching reference design""" + widget = QWidget() + widget.setStyleSheet("background-color: #f5f5f5;") + + main_layout = QVBoxLayout(widget) + main_layout.setContentsMargins(12, 12, 12, 12) + main_layout.setSpacing(12) + + # Main card + card = QFrame() + card.setStyleSheet("QFrame { border: 1px solid #b2b2b2; border-radius: 10px; background-color: #ffffff; }") + card_layout = QVBoxLayout(card) + card_layout.setContentsMargins(16, 16, 16, 16) + card_layout.setSpacing(12) + + label_style = "font-size: 11px; color: #3a3a3a; background: transparent; border: none;" + heading_style = "font-size: 12px; font-weight: 700; color: #2b2b2b; background: transparent; border: none;" + field_width = 120 + + # Deck Design section + deck_title = QLabel("Deck Design:") + deck_title.setStyleSheet(heading_style) + card_layout.addWidget(deck_title) + + deck_grid = QGridLayout() + deck_grid.setContentsMargins(0, 4, 0, 0) + deck_grid.setHorizontalSpacing(12) + deck_grid.setVerticalSpacing(10) + deck_grid.setColumnMinimumWidth(0, 120) + + lbl = QLabel("Reinforcement Size:") + lbl.setStyleSheet(label_style) + self.reinforcement_size_combo = QComboBox() + self.reinforcement_size_combo.addItems(["8 mm", "10 mm", "12 mm", "16 mm", "20 mm"]) + self.reinforcement_size_combo.setFixedWidth(field_width) + apply_field_style(self.reinforcement_size_combo) + deck_grid.addWidget(lbl, 0, 0, Qt.AlignLeft | Qt.AlignVCenter) + deck_grid.addWidget(self.reinforcement_size_combo, 0, 1, Qt.AlignLeft) + + card_layout.addLayout(deck_grid) + + # Shear Studs section + shear_title = QLabel("Shear Studs:") + shear_title.setStyleSheet(heading_style) + card_layout.addWidget(shear_title) + + shear_grid = QGridLayout() + shear_grid.setContentsMargins(0, 4, 0, 0) + shear_grid.setHorizontalSpacing(12) + shear_grid.setVerticalSpacing(10) + shear_grid.setColumnMinimumWidth(0, 120) + + # Material + lbl = QLabel("Material:") + lbl.setStyleSheet(label_style) + self.shear_stud_material_input = QLineEdit() + self.shear_stud_material_input.setFixedWidth(field_width) + apply_field_style(self.shear_stud_material_input) + shear_grid.addWidget(lbl, 0, 0, Qt.AlignLeft | Qt.AlignVCenter) + shear_grid.addWidget(self.shear_stud_material_input, 0, 1, Qt.AlignLeft) + + # Diameter + lbl = QLabel("Diameter (mm):") + lbl.setStyleSheet(label_style) + self.shear_stud_diameter_input = QLineEdit() + self.shear_stud_diameter_input.setFixedWidth(field_width) + apply_field_style(self.shear_stud_diameter_input) + shear_grid.addWidget(lbl, 1, 0, Qt.AlignLeft | Qt.AlignVCenter) + shear_grid.addWidget(self.shear_stud_diameter_input, 1, 1, Qt.AlignLeft) + + # Height + lbl = QLabel("Height (mm):") + lbl.setStyleSheet(label_style) + self.shear_stud_height_input = QLineEdit() + self.shear_stud_height_input.setFixedWidth(field_width) + apply_field_style(self.shear_stud_height_input) + shear_grid.addWidget(lbl, 2, 0, Qt.AlignLeft | Qt.AlignVCenter) + shear_grid.addWidget(self.shear_stud_height_input, 2, 1, Qt.AlignLeft) + + card_layout.addLayout(shear_grid) + card_layout.addStretch() + + main_layout.addWidget(card) + main_layout.addStretch() + + return widget + + def create_placeholder_tab(self, title, description): + """Create a styled placeholder tab with title and description""" + widget = QWidget() + widget.setStyleSheet("background-color: white;") + + layout = QVBoxLayout(widget) + layout.setAlignment(Qt.AlignCenter) + layout.setContentsMargins(40, 40, 40, 40) + + # Icon or visual indicator + icon_label = QLabel("🚧") + icon_label.setStyleSheet("font-size: 48px;") + icon_label.setAlignment(Qt.AlignCenter) + layout.addWidget(icon_label) + + # Title + title_label = QLabel(title) + title_label.setStyleSheet(""" + font-size: 18px; + font-weight: bold; + color: #333; + margin-top: 20px; + margin-bottom: 10px; + """) + title_label.setAlignment(Qt.AlignCenter) + layout.addWidget(title_label) + + # Status + status_label = QLabel("Under Development") + status_label.setStyleSheet(""" + font-size: 14px; + color: #f39c12; + font-weight: bold; + margin-bottom: 20px; + """) + status_label.setAlignment(Qt.AlignCenter) + layout.addWidget(status_label) + + # Description + desc_label = QLabel(description) + desc_label.setStyleSheet(""" + font-size: 12px; + color: #666; + line-height: 1.6; + """) + desc_label.setAlignment(Qt.AlignCenter) + desc_label.setWordWrap(True) + desc_label.setMaximumWidth(600) + layout.addWidget(desc_label) + + layout.addStretch() + layout.addSpacing(10) + action_bar, _, _ = create_action_button_bar(widget) + layout.addWidget(action_bar) + + return widget + + def update_footpath_value(self, footpath_value): + """Update footpath value across all tabs""" + self.footpath_value = footpath_value + self.typical_section_tab.update_footpath_value(footpath_value) diff --git a/src/osdagbridge/desktop/legacy_src/src/osbridge/ui/input_dock.py b/src/osdagbridge/desktop/ui/docks/input_dock.py similarity index 58% rename from src/osdagbridge/desktop/legacy_src/src/osbridge/ui/input_dock.py rename to src/osdagbridge/desktop/ui/docks/input_dock.py index 88febb39..b423b174 100644 --- a/src/osdagbridge/desktop/legacy_src/src/osbridge/ui/input_dock.py +++ b/src/osdagbridge/desktop/ui/docks/input_dock.py @@ -1,15 +1,61 @@ import sys import os +import math from PySide6.QtWidgets import ( QApplication, QWidget, QHBoxLayout, QVBoxLayout, QPushButton, - QComboBox, QScrollArea, QLabel, QFormLayout, QLineEdit, QGroupBox, QSizePolicy, QMessageBox, QInputDialog, QDialog, QCheckBox, QFrame + QComboBox, QScrollArea, QLabel, QFormLayout, QLineEdit, QGroupBox, QSizePolicy, QMessageBox, QInputDialog, QDialog, QCheckBox, QFrame, + QDialogButtonBox, QStackedWidget ) from PySide6.QtCore import Qt, QRegularExpression, QSize from PySide6.QtGui import QPixmap, QDoubleValidator, QRegularExpressionValidator, QIcon from PySide6.QtSvgWidgets import * -from osbridge.backend.common import * -from osbridge.ui.additional_inputs import AdditionalInputsWidget -from osbridge.ui.custom_buttons import DockCustomButton +from osdagbridge.core.utils.common import * +from osdagbridge.desktop.ui.additional_inputs import AdditionalInputs +from osdagbridge.desktop.ui.utils.custom_buttons import DockCustomButton + +STEEL_MEMBER_FIELDS = [ + "Ultimate Tensile Strength, Fu (MPa)", + "Yield Strength, Fy (MPa)", + "Modulus of Elasticity, E (GPa)", + "Modulus of Rigidity, G (GPa)", + "Poisson’s Ratio, ν", + "Thermal Expansion Coefficient, (×10⁻⁶/°C)", +] + +DECK_MEMBER_FIELDS = [ + "Characteristic Compressive (Cube) Strength of Concrete, (fck)cu (MPa)", + "Mean Tensile Strength of Concrete, fctm (MPa)", + "Secant Modulus of Elasticity of Concrete, Ecm (GPa)", + "Ecm Multiplication Factor", +] + +STEEL_MODULUS_E_GPA = 200.0 +STEEL_MODULUS_G_GPA = 77.0 +STEEL_POISSON_RATIO = 0.30 +STEEL_THERMAL_COEFF = 11.7 + +STEEL_GRADE_BASE_VALUES = { + 250: {"Fy": 250, "Fu": 410}, + 275: {"Fy": 275, "Fu": 430}, + 300: {"Fy": 300, "Fu": 440}, + 350: {"Fy": 350, "Fu": 490}, + 410: {"Fy": 410, "Fu": 540}, + 450: {"Fy": 450, "Fu": 570}, + 550: {"Fy": 550, "Fu": 650}, + 600: {"Fy": 600, "Fu": 700}, + 650: {"Fy": 650, "Fu": 750}, +} + +ECM_FACTOR_OPTIONS = [ + ("Quartzite/granite aggregates = 1", 1.0), + ("Limestone aggregates = 0.9", 0.9), + ("Sandstone aggregates = 0.7", 0.7), + ("Basalt aggregates = 1.2", 1.2), + ("Custom", None), +] +ECM_FACTOR_LABELS = [text for text, _ in ECM_FACTOR_OPTIONS] +DEFAULT_ECM_FACTOR_LABEL = ECM_FACTOR_OPTIONS[0][0] +CUSTOM_ECM_FACTOR_LABEL = "Custom" class NoScrollComboBox(QComboBox): @@ -23,7 +69,7 @@ def apply_field_style(widget): if isinstance(widget, QComboBox): style = """ QComboBox{ - padding: 2px; + padding: 1px 7px; border: 1px solid black; border-radius: 5px; background-color: white; @@ -88,6 +134,481 @@ def apply_field_style(widget): } """) + +class MaterialPropertiesDialog(QDialog): + MEMBER_OPTIONS = ["Girder", "Cross Bracing", "End Diaphragm", "Deck"] + STEEL_MEMBERS = {"Girder", "Cross Bracing", "End Diaphragm"} + + def __init__(self, parent=None): + super().__init__(parent) + self.setWindowTitle("Material Properties") + self.setMinimumWidth(580) + self.setStyleSheet("background-color: white;") + + self.parent_dock = parent + self._loading = False + self.current_member = None + self.member_data = {} + + self.member_combo = NoScrollComboBox() + self.member_combo.addItems(self.MEMBER_OPTIONS) + apply_field_style(self.member_combo) + + self.material_combo = NoScrollComboBox() + apply_field_style(self.material_combo) + + main_layout = QVBoxLayout(self) + main_layout.setContentsMargins(20, 16, 20, 16) + + # Create a container widget for all form fields + form_container = QWidget() + form_layout = QVBoxLayout(form_container) + form_layout.setContentsMargins(0, 0, 0, 0) + form_layout.setSpacing(10) + + # Member row + member_row = QHBoxLayout() + member_row.setContentsMargins(0, 0, 0, 0) + member_row.setSpacing(18) + member_label = QLabel("Member*:") + member_label.setStyleSheet("font-size: 12px; color: #2d2d2d;") + member_label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) + member_label.setFixedWidth(280) + self.member_combo.setFixedWidth(242) + member_row.addWidget(member_label) + member_row.addWidget(self.member_combo) + member_row.addStretch() + form_layout.addLayout(member_row) + + # Material row + material_row = QHBoxLayout() + material_row.setContentsMargins(0, 0, 0, 0) + material_row.setSpacing(18) + material_label = QLabel("Material*:") + material_label.setStyleSheet("font-size: 12px; color: #2d2d2d;") + material_label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) + material_label.setFixedWidth(280) + self.material_combo.setFixedWidth(242) + material_row.addWidget(material_label) + material_row.addWidget(self.material_combo) + material_row.addStretch() + form_layout.addLayout(material_row) + + main_layout.addWidget(form_container) + + self.stack = QStackedWidget() + self.stack.setContentsMargins(0, 0, 0, 0) + self.steel_page = self._build_steel_form() + self.deck_page = self._build_deck_form() + self.stack.addWidget(self.steel_page) + self.stack.addWidget(self.deck_page) + main_layout.addWidget(self.stack) + + # Updated default row with proper alignment + default_row = QHBoxLayout() + default_row.setContentsMargins(0, 0, 0, 0) + default_row.setSpacing(18) + default_label = QLabel("Default") + default_label.setStyleSheet("font-size: 12px; color: #2d2d2d;") + default_label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) + default_label.setFixedWidth(280) + self.default_checkbox = QCheckBox() + # Create container for checkbox to align it to the left + checkbox_container = QWidget() + checkbox_layout = QHBoxLayout(checkbox_container) + checkbox_layout.setContentsMargins(0, 0, 0, 0) + checkbox_layout.setSpacing(0) + checkbox_layout.addWidget(self.default_checkbox) + checkbox_layout.addStretch() + + default_row.addWidget(default_label) + default_row.addWidget(checkbox_container) + main_layout.addLayout(default_row) + + self.member_combo.currentTextChanged.connect(self._on_member_changed) + self.material_combo.currentTextChanged.connect(self._on_material_changed) + self.default_checkbox.stateChanged.connect(self._on_default_toggled) + + self._initialize_member_data() + self._on_member_changed(self.member_combo.currentText()) + + def closeEvent(self, event): + self._save_current_member_form() + super().closeEvent(event) + + def _build_steel_form(self): + widget = QWidget() + layout = QVBoxLayout(widget) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(10) + self.steel_field_inputs = {} + for label_text in STEEL_MEMBER_FIELDS: + row = QHBoxLayout() + row.setContentsMargins(0, 0, 0, 0) + row.setSpacing(18) + label = QLabel(label_text) + label.setStyleSheet("font-size: 12px; color: #2d2d2d;") + label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) + label.setFixedWidth(280) + line_edit = QLineEdit() + line_edit.setFixedWidth(242) + apply_field_style(line_edit) + # Add validator for 1 decimal place + line_edit.setValidator(QDoubleValidator(0.0, 99999.0, 1)) + line_edit.textEdited.connect(self._handle_user_override) + self.steel_field_inputs[label_text] = line_edit + row.addWidget(label) + row.addWidget(line_edit) + row.addStretch() + layout.addLayout(row) + layout.addStretch() + return widget + + def _build_deck_form(self): + widget = QWidget() + layout = QVBoxLayout(widget) + layout.setSpacing(10) + self.deck_field_inputs = {} + for label_text in DECK_MEMBER_FIELDS: + row = QHBoxLayout() + row.setSpacing(18) + label = QLabel(label_text) + label.setStyleSheet("font-size: 12px; color: #2d2d2d;") + label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) + label.setFixedWidth(280) + if label_text == "Ecm Multiplication Factor": + self.deck_factor_combo = NoScrollComboBox() + self.deck_factor_combo.addItems(ECM_FACTOR_LABELS) + self.deck_factor_combo.setFixedWidth(242) + apply_field_style(self.deck_factor_combo) + self.deck_factor_combo.currentTextChanged.connect(self._on_factor_changed) + + self.deck_factor_custom_input = QLineEdit() + apply_field_style(self.deck_factor_custom_input) + self.deck_factor_custom_input.setPlaceholderText("Custom factor") + self.deck_factor_custom_input.setFixedWidth(242) + self.deck_factor_custom_input.setVisible(False) + self.deck_factor_custom_input.setEnabled(False) + self.deck_factor_custom_input.setValidator(QDoubleValidator(0.1, 5.0, 1)) + self.deck_factor_custom_input.textEdited.connect(self._handle_user_override) + + row.addWidget(label) + row.addWidget(self.deck_factor_combo) + row.addStretch() + + # Add custom input row (hidden by default) + custom_row = QHBoxLayout() + custom_row.setContentsMargins(0, 0, 0, 0) + custom_row.setSpacing(18) + custom_label = QLabel("") # Empty label for alignment + custom_label.setFixedWidth(280) + custom_row.addWidget(custom_label) + custom_row.addWidget(self.deck_factor_custom_input) + custom_row.addStretch() + layout.addLayout(custom_row) + + self.deck_field_inputs[label_text] = self.deck_factor_combo + else: + line_edit = QLineEdit() + line_edit.setFixedWidth(242) + apply_field_style(line_edit) + # Add validator for 1 decimal place + line_edit.setValidator(QDoubleValidator(0.0, 99999.0, 1)) + line_edit.textEdited.connect(self._handle_user_override) + row.addWidget(label) + row.addWidget(line_edit) + row.addStretch() + self.deck_field_inputs[label_text] = line_edit + layout.addLayout(row) + layout.addStretch() + return widget + + def _initialize_member_data(self): + for member in self.MEMBER_OPTIONS: + material = self._get_parent_grade(member) + fields = self._default_fields_for_member(member, material) + self.member_data[member] = { + "material": material, + "fields": fields, + "is_default": True, + "factor_label": DEFAULT_ECM_FACTOR_LABEL if member == "Deck" else None, + "custom_factor": "1.0" if member == "Deck" else None, + } + + def _default_fields_for_member(self, member, material=None, factor_label=None, custom_factor=None): + if member == "Deck": + grade = material or self._get_parent_grade(member) or (VALUES_DECK_CONCRETE_GRADE[0] if VALUES_DECK_CONCRETE_GRADE else "") + factor_label = factor_label or DEFAULT_ECM_FACTOR_LABEL + factor_value = self._factor_value_from_label(factor_label, custom_factor) + return self._deck_defaults(grade, factor_value) + grade = material or self._get_parent_grade(member) or (VALUES_MATERIAL[0] if VALUES_MATERIAL else "") + return self._steel_defaults(grade) + + def _steel_defaults(self, grade): + grade_value = self._extract_numeric_grade(grade) + defaults = STEEL_GRADE_BASE_VALUES.get(grade_value, STEEL_GRADE_BASE_VALUES[250]) + return { + "Ultimate Tensile Strength, Fu (MPa)": "{:.1f}".format(defaults["Fu"]), + "Yield Strength, Fy (MPa)": "{:.1f}".format(defaults["Fy"]), + "Modulus of Elasticity, E (GPa)": "{:.1f}".format(STEEL_MODULUS_E_GPA), + "Modulus of Rigidity, G (GPa)": "{:.1f}".format(STEEL_MODULUS_G_GPA), + "Poisson's Ratio, ν": "{:.1f}".format(STEEL_POISSON_RATIO), + "Thermal Expansion Coefficient, (×10⁻⁶/°C)": "{:.1f}".format(STEEL_THERMAL_COEFF), + } + + def _deck_defaults(self, grade, factor_value): + strength = self._extract_numeric_grade(grade, default=25) + fck = float(strength) + fctm = round(0.7 * math.sqrt(fck), 1) + ecm = round(5.0 * math.sqrt(fck) * factor_value, 1) + return { + "Characteristic Compressive (Cube) Strength of Concrete, (fck)cu (MPa)": "{:.1f}".format(fck), + "Mean Tensile Strength of Concrete, fctm (MPa)": "{:.1f}".format(fctm), + "Secant Modulus of Elasticity of Concrete, Ecm (GPa)": "{:.1f}".format(ecm), + "Ecm Multiplication Factor": "{:.1f}".format(factor_value), + } + + def _extract_numeric_grade(self, grade, default=250): + digits = ''.join(ch for ch in grade if ch.isdigit()) + try: + return int(digits) if digits else default + except ValueError: + return default + + def _materials_for_member(self, member): + return VALUES_DECK_CONCRETE_GRADE if member == "Deck" else VALUES_MATERIAL + + def _on_member_changed(self, member): + if self.current_member: + self._save_current_member_form() + + self.current_member = member + is_deck = member == "Deck" + self.stack.setCurrentWidget(self.deck_page if is_deck else self.steel_page) + + data = self.member_data.get(member) + if not data: + self.member_data[member] = self._create_default_entry(member) + data = self.member_data[member] + + if data.get("is_default"): + self._apply_defaults_for_member(member, update_ui=False) + + materials = self._materials_for_member(member) + self._loading = True + self.material_combo.clear() + self.material_combo.addItems(materials) + if data["material"] in materials: + self.material_combo.setCurrentText(data["material"]) + elif materials: + self.material_combo.setCurrentIndex(0) + data["material"] = self.material_combo.currentText() + + self.default_checkbox.setChecked(data.get("is_default", False)) + if is_deck: + self._populate_deck_fields(data) + else: + self._populate_steel_fields(data) + self._loading = False + + def _populate_steel_fields(self, data): + for label, widget in self.steel_field_inputs.items(): + value = data["fields"].get(label, "") + # Format to 1 decimal place + try: + formatted_value = "{:.1f}".format(float(value)) + widget.setText(formatted_value) + except (ValueError, TypeError): + widget.setText(value) + + def _populate_deck_fields(self, data): + for label, widget in self.deck_field_inputs.items(): + if label == "Ecm Multiplication Factor": + factor_label = data.get("factor_label", DEFAULT_ECM_FACTOR_LABEL) + if factor_label not in ECM_FACTOR_LABELS: + factor_label = DEFAULT_ECM_FACTOR_LABEL + self.deck_factor_combo.blockSignals(True) + self.deck_factor_combo.setCurrentText(factor_label) + self.deck_factor_combo.blockSignals(False) + self._update_custom_factor_visibility(factor_label) + self.deck_factor_custom_input.blockSignals(True) + custom_val = data.get("custom_factor", "1.0") + try: + formatted_custom = "{:.1f}".format(float(custom_val)) + self.deck_factor_custom_input.setText(formatted_custom) + except (ValueError, TypeError): + self.deck_factor_custom_input.setText(custom_val) + self.deck_factor_custom_input.blockSignals(False) + else: + value = data["fields"].get(label, "") + # Format to 1 decimal place + try: + formatted_value = "{:.1f}".format(float(value)) + widget.setText(formatted_value) + except (ValueError, TypeError): + widget.setText(value) + + def _save_current_member_form(self): + if not self.current_member: + return + data = self.member_data.setdefault(self.current_member, self._create_default_entry(self.current_member)) + data["material"] = self.material_combo.currentText() + if self.current_member == "Deck": + for label, widget in self.deck_field_inputs.items(): + if label == "Ecm Multiplication Factor": + data["factor_label"] = self.deck_factor_combo.currentText() + data["custom_factor"] = self.deck_factor_custom_input.text() or "1.0" + else: + data["fields"][label] = widget.text() + factor_value = self._factor_value_from_label(data["factor_label"], data.get("custom_factor")) + data["fields"]["Ecm Multiplication Factor"] = "{:.1f}".format(factor_value) + else: + for label, widget in self.steel_field_inputs.items(): + data["fields"][label] = widget.text() + data["is_default"] = self.default_checkbox.isChecked() + + def _create_default_entry(self, member): + material = self._get_parent_grade(member) + return { + "material": material, + "fields": self._default_fields_for_member(member, material), + "is_default": True, + "factor_label": DEFAULT_ECM_FACTOR_LABEL if member == "Deck" else None, + "custom_factor": "1.0" if member == "Deck" else None, + } + + def _apply_defaults_for_member(self, member, update_ui=True): + data = self.member_data.setdefault(member, self._create_default_entry(member)) + grade = self._get_parent_grade(member) or data.get("material") + materials = self._materials_for_member(member) + if grade not in materials and materials: + grade = materials[0] + data["material"] = grade + if member == "Deck": + data["factor_label"] = DEFAULT_ECM_FACTOR_LABEL + data["custom_factor"] = "1.0" + factor_value = self._factor_value_from_label(DEFAULT_ECM_FACTOR_LABEL) + data["fields"] = self._deck_defaults(grade, factor_value) + else: + data["fields"] = self._steel_defaults(grade) + data["is_default"] = True + + if update_ui and member == self.current_member: + self._loading = True + self.material_combo.setCurrentText(grade) + if member == "Deck": + self._populate_deck_fields(data) + else: + self._populate_steel_fields(data) + self.default_checkbox.setChecked(True) + self._loading = False + + def _factor_value_from_label(self, label, custom_factor=None): + for text, value in ECM_FACTOR_OPTIONS: + if text == label: + if value is None: + try: + return float(custom_factor) if custom_factor else 1.0 + except ValueError: + return 1.0 + return value + return 1.0 + + def _reset_current_member_to_defaults(self): + if not self.current_member: + return + + self._apply_defaults_for_member(self.current_member, update_ui=False) + data = self.member_data.get(self.current_member) + if not data: + return + + target_material = data.get("material", "") + self._loading = True + if target_material: + index = self.material_combo.findText(target_material) + if index >= 0: + self.material_combo.setCurrentIndex(index) + elif self.material_combo.count() > 0: + self.material_combo.setCurrentIndex(0) + data["material"] = self.material_combo.currentText() + if self.current_member == "Deck": + self._populate_deck_fields(data) + else: + self._populate_steel_fields(data) + self._loading = False + + self.default_checkbox.blockSignals(True) + self.default_checkbox.setChecked(True) + self.default_checkbox.blockSignals(False) + self._save_current_member_form() + + def _update_custom_factor_visibility(self, label): + is_custom = label == CUSTOM_ECM_FACTOR_LABEL + self.deck_factor_custom_input.setVisible(is_custom) + self.deck_factor_custom_input.setEnabled(is_custom) + self.deck_factor_combo.setVisible(not is_custom) + + def _on_material_changed(self, material): + if self._loading: + return + data = self.member_data.get(self.current_member) + if data: + data["material"] = material + self._handle_user_override() + + def _on_default_toggled(self, state): + if self._loading: + return + try: + check_state = Qt.CheckState(state) + except ValueError: + check_state = Qt.CheckState.Checked if bool(state) else Qt.CheckState.Unchecked + if check_state == Qt.CheckState.Checked: + self._reset_current_member_to_defaults() + else: + data = self.member_data.get(self.current_member) + if data: + data["is_default"] = False + + def _on_factor_changed(self, label): + self._update_custom_factor_visibility(label) + self._handle_user_override() + + def _handle_user_override(self): + if self._loading: + return + if self.default_checkbox.isChecked(): + self._loading = True + self.default_checkbox.setChecked(False) + self._loading = False + data = self.member_data.get(self.current_member) + if data: + data["is_default"] = False + self._save_current_member_form() + + def _get_parent_grade(self, member): + parent = self.parent_dock + if not parent: + return "" + mapping = { + "Girder": getattr(parent, "girder_combo", None), + "Cross Bracing": getattr(parent, "cross_bracing_combo", None), + "End Diaphragm": getattr(parent, "end_diaphragm_combo", None), + "Deck": getattr(parent, "deck_combo", None), + } + combo = mapping.get(member) + return combo.currentText() if combo else "" + + def set_member(self, member): + index = self.member_combo.findText(member) + if index >= 0: + self.member_combo.setCurrentIndex(index) + + def sync_with_parent_defaults(self): + for member, data in self.member_data.items(): + if data.get("is_default"): + self._apply_defaults_for_member(member, update_ui=(member == self.current_member)) def create_group_box(title): """Create a styled group box""" group_box = QGroupBox(title) @@ -148,8 +669,15 @@ def __init__(self, backend, parent): self.structure_type_combo = None self.project_location_combo = None self.custom_location_input = None + self.include_median_combo = None self.footpath_combo = None self.additional_inputs_window = None + self.additional_inputs_widget = None + self.material_dialog = None + self.additional_inputs_btn = None + self.lock_button = None + self.scroll_area = None + self.is_locked = False self.setStyleSheet("background: transparent;") self.main_layout = QHBoxLayout(self) @@ -285,28 +813,6 @@ def show_project_location_dialog(self): coords_row.setSpacing(15) self.coords_checkbox = QCheckBox("Enter Coordinates") - self.coords_checkbox.setStyleSheet(""" - QCheckBox { - font-size: 12px; - font-weight: normal; - color: black; - spacing: 8px; - } - QCheckBox::indicator { - width: 18px; - height: 18px; - border: 2px solid #b0b0b0; - border-radius: 3px; - background-color: white; - } - QCheckBox::indicator:checked { - background-color: #90AF13; - border-color: #90AF13; - } - QCheckBox::indicator:hover { - border-color: #7a9a12; - } - """) coords_row.addWidget(self.coords_checkbox) coords_row.addStretch() @@ -345,28 +851,6 @@ def show_project_location_dialog(self): location_row.setSpacing(15) self.location_checkbox = QCheckBox("Enter Location Name") - self.location_checkbox.setStyleSheet(""" - QCheckBox { - font-size: 12px; - font-weight: normal; - color: black; - spacing: 8px; - } - QCheckBox::indicator { - width: 18px; - height: 18px; - border: 2px solid #b0b0b0; - border-radius: 3px; - background-color: white; - } - QCheckBox::indicator:checked { - background-color: #90AF13; - border-color: #90AF13; - } - QCheckBox::indicator:hover { - border-color: #7a9a12; - } - """) location_row.addWidget(self.location_checkbox) location_row.addStretch() @@ -407,28 +891,6 @@ def show_project_location_dialog(self): map_section.setSpacing(8) self.map_checkbox = QCheckBox("Select on Map") - self.map_checkbox.setStyleSheet(""" - QCheckBox { - font-size: 12px; - font-weight: normal; - color: black; - spacing: 8px; - } - QCheckBox::indicator { - width: 18px; - height: 18px; - border: 2px solid #b0b0b0; - border-radius: 3px; - background-color: white; - } - QCheckBox::indicator:checked { - background-color: #90AF13; - border-color: #90AF13; - } - QCheckBox::indicator:hover { - border-color: #7a9a12; - } - """) map_section.addWidget(self.map_checkbox) # Map placeholder @@ -487,27 +949,6 @@ def show_project_location_dialog(self): # === Custom Loading Parameters Checkbox === self.custom_params_checkbox = QCheckBox("Tabulate Custom Loading Parameters") - self.custom_params_checkbox.setStyleSheet(""" - QCheckBox { - font-size: 11px; - color: black; - spacing: 8px; - } - QCheckBox::indicator { - width: 18px; - height: 18px; - border: 2px solid #b0b0b0; - border-radius: 3px; - background-color: white; - } - QCheckBox::indicator:checked { - background-color: #90AF13; - border-color: #90AF13; - } - QCheckBox::indicator:hover { - border-color: #7a9a12; - } - """) main_layout.addWidget(self.custom_params_checkbox) main_layout.addStretch() @@ -631,9 +1072,9 @@ def build_left_panel(self, field_list): top_bar.addWidget(input_dock_btn) # Additional Inputs button with lock icon on the right - additional_inputs_btn = QPushButton("Additional Inputs") - additional_inputs_btn.setCursor(Qt.CursorShape.PointingHandCursor) - additional_inputs_btn.setStyleSheet(""" + self.additional_inputs_btn = QPushButton("Additional Inputs") + self.additional_inputs_btn.setCursor(Qt.CursorShape.PointingHandCursor) + self.additional_inputs_btn.setStyleSheet(""" QPushButton { background-color: white; color: black; @@ -655,33 +1096,23 @@ def build_left_panel(self, field_list): border: 1px solid black; } """) - additional_inputs_btn.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) - additional_inputs_btn.clicked.connect(self.show_additional_inputs) - top_bar.addWidget(additional_inputs_btn) + self.additional_inputs_btn.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) + self.additional_inputs_btn.clicked.connect(self.show_additional_inputs) + top_bar.addWidget(self.additional_inputs_btn) # Load the icon from resources - lock_button = QPushButton() - lock_button.setCursor(Qt.CursorShape.PointingHandCursor) - lock_state = [1] # Use list to make it mutable - lock_button.setIcon(QIcon(":/vectors/lock_close.svg")) - lock_button.setIconSize(QSize(30, 30)) - lock_button.setStyleSheet("border: none;") - top_bar.addWidget(lock_button) - - def toggle_lock(): - if lock_state[0] == 0: - lock_state[0] = 1 - lock_button.setIcon(QIcon(":/vectors/lock_close.svg")) - elif lock_state[0] == 1: - lock_state[0] = 0 - lock_button.setIcon(QIcon(":/vectors/lock_open.svg")) - - lock_button.clicked.connect(toggle_lock) + self.lock_button = QPushButton() + self.lock_button.setCursor(Qt.CursorShape.PointingHandCursor) + self.lock_button.setIconSize(QSize(30, 30)) + self.lock_button.setStyleSheet("border: none;") + self.lock_button.clicked.connect(self._toggle_lock_state) + top_bar.addWidget(self.lock_button) panel_layout.addLayout(top_bar) # Scroll area scroll_area = QScrollArea() + self.scroll_area = scroll_area scroll_area.setWidgetResizable(True) scroll_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) scroll_area.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) @@ -811,7 +1242,7 @@ def toggle_lock(): location_box_layout.setSpacing(8) loc_header = QHBoxLayout() - loc_title = QLabel("Project Location") + loc_title = QLabel("Project Location*") loc_title.setStyleSheet(""" QLabel { color: #000000; @@ -942,7 +1373,7 @@ def _toggle_structure(checked): # Span span_row = QHBoxLayout() - span_label = QLabel("Span (m)") + span_label = QLabel("Span*") span_label.setStyleSheet(""" QLabel { color: #000000; @@ -962,7 +1393,7 @@ def _toggle_structure(checked): # Carriageway Width carriageway_row = QHBoxLayout() - carriageway_label = QLabel("Carriageway (m)") + carriageway_label = QLabel("Carriageway Width*") carriageway_label.setStyleSheet(""" QLabel { color: #000000; @@ -974,11 +1405,39 @@ def _toggle_structure(checked): self.carriageway_input = QLineEdit() self.carriageway_input.setObjectName(KEY_CARRIAGEWAY_WIDTH) apply_field_style(self.carriageway_input) - self.carriageway_input.setValidator(QDoubleValidator(CARRIAGEWAY_WIDTH_MIN, 100.0, 2)) - self.carriageway_input.setPlaceholderText(f"Min {CARRIAGEWAY_WIDTH_MIN} m") + self.carriageway_input.setValidator(QDoubleValidator(0.0, 100.0, 2)) + self.carriageway_input.editingFinished.connect(self.validate_carriageway_width) carriageway_row.addWidget(carriageway_label) carriageway_row.addWidget(self.carriageway_input, 1) geo_box_layout.addLayout(carriageway_row) + + # Include Median option + median_row = QHBoxLayout() + median_row.setContentsMargins(0, 0, 0, 0) + median_row.setSpacing(8) + + median_label = QLabel("Include Median") + median_label.setStyleSheet(""" + QLabel { + color: #000000; + font-size: 12px; + background: transparent; + } + """) + median_label.setMinimumWidth(110) + median_row.addWidget(median_label) + + self.include_median_combo = NoScrollComboBox() + self.include_median_combo.addItems(["No", "Yes"]) + self.include_median_combo.setCurrentIndex(0) + self.include_median_combo.setObjectName(KEY_INCLUDE_MEDIAN) + apply_field_style(self.include_median_combo) + #self.include_median_combo.setMaximumWidth(110) + self.include_median_combo.currentTextChanged.connect(self.on_include_median_changed) + median_row.addWidget(self.include_median_combo, 1) + median_row.addStretch() + geo_box_layout.addLayout(median_row) + self._update_carriageway_placeholder() # Footpath footpath_row = QHBoxLayout() @@ -1016,8 +1475,8 @@ def _toggle_structure(checked): self.skew_input.setObjectName(KEY_SKEW_ANGLE) apply_field_style(self.skew_input) self.skew_input.setValidator(QDoubleValidator(SKEW_ANGLE_MIN, SKEW_ANGLE_MAX, 1)) - self.skew_input.setText(str(SKEW_ANGLE_DEFAULT)) - self.skew_input.setPlaceholderText(f"Default: {SKEW_ANGLE_DEFAULT}°") + #self.skew_input.setText(f"{str(SKEW_ANGLE_DEFAULT)}°") + self.skew_input.setPlaceholderText(f"{SKEW_ANGLE_MIN} - {SKEW_ANGLE_MAX}°") skew_row.addWidget(skew_label) skew_row.addWidget(self.skew_input, 1) geo_box_layout.addLayout(skew_row) @@ -1122,6 +1581,25 @@ def _toggle_structure(checked): cross_bracing_row.addWidget(cross_bracing_label) cross_bracing_row.addWidget(self.cross_bracing_combo, 1) material_box_layout.addLayout(cross_bracing_row) + + # End Diaphragm + end_diaphragm_row = QHBoxLayout() + end_diaphragm_label = QLabel("End Diaphragm") + end_diaphragm_label.setStyleSheet(""" + QLabel { + color: #000000; + font-size: 12px; + background: transparent; + } + """) + end_diaphragm_label.setMinimumWidth(110) + self.end_diaphragm_combo = NoScrollComboBox() + self.end_diaphragm_combo.setObjectName(KEY_END_DIAPHRAGM) + apply_field_style(self.end_diaphragm_combo) + self.end_diaphragm_combo.addItems(VALUES_MATERIAL) + end_diaphragm_row.addWidget(end_diaphragm_label) + end_diaphragm_row.addWidget(self.end_diaphragm_combo, 1) + material_box_layout.addLayout(end_diaphragm_row) # Deck deck_row = QHBoxLayout() @@ -1138,14 +1616,14 @@ def _toggle_structure(checked): self.deck_combo.setObjectName(KEY_DECK_CONCRETE_GRADE_BASIC) apply_field_style(self.deck_combo) self.deck_combo.addItems(VALUES_DECK_CONCRETE_GRADE) - self.deck_combo.setCurrentText("M25") + self.deck_combo.setCurrentText("M 25") deck_row.addWidget(deck_label) deck_row.addWidget(self.deck_combo, 1) material_box_layout.addLayout(deck_row) # Material Properties header with button mat_prop_header = QHBoxLayout() - mat_prop_title = QLabel("Modify Properties") + mat_prop_title = QLabel("Properties") mat_prop_title.setStyleSheet(""" QLabel { color: #000000; @@ -1173,6 +1651,7 @@ def _toggle_structure(checked): background-color: #7a9a12; } """) + modify_mat_btn.clicked.connect(self.show_material_properties_dialog) mat_prop_header.addWidget(modify_mat_btn, 1) material_box_layout.addLayout(mat_prop_header) @@ -1197,6 +1676,7 @@ def _toggle_structure(checked): sub_layout.setContentsMargins(10, 10, 10, 10) sub_layout.setSpacing(8) + # Header with toggle sub_header = QHBoxLayout() sub_title = QLabel("Substructure") sub_title.setStyleSheet("font-size: 13px; font-weight: bold; color: #333;") @@ -1297,38 +1777,163 @@ def _toggle_sub(checked): h_scroll_area.setWidget(self.left_panel) left_layout.addWidget(h_scroll_area) + self._apply_lock_state() def show_additional_inputs(self): """Show Additional Inputs dialog""" footpath_value = self.footpath_combo.currentText() if self.footpath_combo else "None" - carriageway_width = 7.5 - if self.input_widget: - carriageway_field = self.input_widget.findChild(QLineEdit, KEY_CARRIAGEWAY_WIDTH) - if carriageway_field and carriageway_field.text(): - try: - carriageway_width = float(carriageway_field.text()) - except ValueError: - carriageway_width = 7.5 + carriageway_width = self._get_effective_carriageway_width() if self.additional_inputs_window is None or not self.additional_inputs_window.isVisible(): self.additional_inputs_window = QDialog(self) + self.additional_inputs_window.setObjectName("AdditionalInputs") self.additional_inputs_window.setWindowTitle("Additional Inputs - Manual Bridge Parameter Definition") - self.additional_inputs_window.resize(900, 700) + self.additional_inputs_window.resize(1024, 720) + self.additional_inputs_window.setMinimumSize(820, 520) + self.additional_inputs_window.setSizeGripEnabled(True) layout = QVBoxLayout(self.additional_inputs_window) layout.setContentsMargins(0, 0, 0, 0) - self.additional_inputs_widget = AdditionalInputsWidget(footpath_value, carriageway_width, self.additional_inputs_window) - layout.addWidget(self.additional_inputs_widget) + scroll_area = QScrollArea(self.additional_inputs_window) + scroll_area.setWidgetResizable(True) + scroll_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + scroll_area.setFrameShape(QFrame.NoFrame) + scroll_area.setStyleSheet("QScrollArea { border: none; background: transparent; }") + layout.addWidget(scroll_area) + + self.additional_inputs_widget = AdditionalInputs(footpath_value, carriageway_width) + scroll_area.setWidget(self.additional_inputs_widget) + self.additional_inputs_window.destroyed.connect(lambda _=None: self._handle_additional_inputs_closed()) + self._set_additional_inputs_enabled(not self.is_locked) self.additional_inputs_window.show() else: self.additional_inputs_window.raise_() self.additional_inputs_window.activateWindow() + self._set_additional_inputs_enabled(not self.is_locked) + def _toggle_lock_state(self): + self.is_locked = not self.is_locked + self._apply_lock_state() + + def _apply_lock_state(self): + if self.lock_button: + icon_path = ":/vectors/lock_close.svg" if self.is_locked else ":/vectors/lock_open.svg" + self.lock_button.setIcon(QIcon(icon_path)) + + enabled = not self.is_locked + if self.scroll_area: + self.scroll_area.setEnabled(enabled) + if self.input_widget: + self.input_widget.setEnabled(enabled) + self._set_additional_inputs_enabled(enabled) + + if self.material_dialog: + self.material_dialog.setEnabled(enabled) + + def _set_additional_inputs_enabled(self, enabled): + if self.additional_inputs_widget: + self.additional_inputs_widget.setEnabled(enabled) + + def _handle_additional_inputs_closed(self): + self.additional_inputs_window = None + self.additional_inputs_widget = None + def on_footpath_changed(self, footpath_value): """Update additional inputs when footpath changes""" if self.additional_inputs_window and self.additional_inputs_window.isVisible(): if hasattr(self, 'additional_inputs_widget'): - self.additional_inputs_widget.update_footpath_value(footpath_value) \ No newline at end of file + self.additional_inputs_widget.update_footpath_value(footpath_value) + + def on_include_median_changed(self, _value): + self._update_carriageway_placeholder() + # Re-validate silently so previously entered values honor the new limits + self.validate_carriageway_width(show_message=False) + + def _carriageway_limits(self): + include_median = self._is_median_included() + min_width = CARRIAGEWAY_WIDTH_MIN_WITH_MEDIAN if include_median else CARRIAGEWAY_WIDTH_MIN + return min_width, CARRIAGEWAY_WIDTH_MAX_LIMIT + + def _update_carriageway_placeholder(self): + if not hasattr(self, "carriageway_input") or self.carriageway_input is None: + return + min_width, max_width = self._carriageway_limits() + suffix = " per side" if self._is_median_included() else "" + self.carriageway_input.setPlaceholderText(f"{min_width:.2f} - {max_width:.1f} m{suffix}") + + def validate_carriageway_width(self, show_message=True): + if not self.carriageway_input: + return + text = self.carriageway_input.text().strip() + if not text: + return + try: + value = float(text) + except ValueError: + self.carriageway_input.clear() + if show_message: + QMessageBox.warning(self, "Carriageway Width", "Please enter a numeric carriageway width.") + return + + min_width, max_width = self._carriageway_limits() + include_median = self._is_median_included() + message = None + + if value < min_width: + if include_median: + message = "IRC 5 Clause 104.3.1 requires minimum carriageway width on both sides of the median to be at least 7.5 m." + else: + message = "IRC 5 Clause 104.3.1 requires minimum carriageway width of 4.25 m." + value = min_width + elif value > max_width: + message = "Software limits carriageway width upto 23.6 m" + value = max_width + + self.carriageway_input.setText(f"{value:.2f}") + if message and show_message: + QMessageBox.warning(self, "Carriageway Width", message) + + def _get_effective_carriageway_width(self): + min_width, max_width = self._carriageway_limits() + width = min_width + if self.carriageway_input and self.carriageway_input.text(): + try: + width = float(self.carriageway_input.text()) + except ValueError: + width = min_width + width = max(min_width, min(width, max_width)) + if self._is_median_included(): + return width * 2.0 # Two carriageways, one on each side of the median + return width + + def _is_median_included(self): + if not self.include_median_combo: + return False + return self.include_median_combo.currentText().lower() == "yes" + + def show_material_properties_dialog(self): + """Open the material properties dialog with the relevant member selected.""" + if self.material_dialog is None: + self.material_dialog = MaterialPropertiesDialog(self) + + member = "Girder" + focus_widget = QApplication.focusWidget() + focus_map = { + getattr(self, 'girder_combo', None): "Girder", + getattr(self, 'deck_combo', None): "Deck", + getattr(self, 'cross_bracing_combo', None): "Cross Bracing", + getattr(self, 'end_diaphragm_combo', None): "End Diaphragm", + } + for widget, name in focus_map.items(): + if widget is not None and widget is focus_widget: + member = name + break + + self.material_dialog.sync_with_parent_defaults() + self.material_dialog.set_member(member) + self.material_dialog.show() + self.material_dialog.raise_() + self.material_dialog.activateWindow() \ No newline at end of file diff --git a/src/osdagbridge/desktop/ui/docks/log_dock.py b/src/osdagbridge/desktop/ui/docks/log_dock.py new file mode 100644 index 00000000..5c16189b --- /dev/null +++ b/src/osdagbridge/desktop/ui/docks/log_dock.py @@ -0,0 +1,91 @@ +""" +Log dock widget for Osdag GUI. +Displays log messages and status updates. +""" +from PySide6.QtWidgets import QWidget, QVBoxLayout, QTextEdit, QLabel +from PySide6.QtCore import Qt, QDateTime + +class LogDock(QWidget): + def __init__(self, parent=None): + super().__init__(parent) + # Ensures automatic deletion when closed + self.setAttribute(Qt.WA_DeleteOnClose, True) + self.is_visible = True + self.setObjectName("logs_dock") + self.init_ui() + self.adjust_size() + + def init_ui(self): + # Create layout for the log dock + layout = QVBoxLayout(self) + layout.setContentsMargins(2, 2, 2, 0) + layout.setSpacing(0) + + # Create a top strip for "Log Window" + self.log_window_title = QLabel("Log Window") + self.log_window_title.setAlignment(Qt.AlignLeft) + layout.addWidget(self.log_window_title) + + # Create log display area + self.log_display = QTextEdit() + self.log_display.setObjectName("textEdit") + self.log_display.setReadOnly(True) + self.log_display.setOverwriteMode(True) + layout.addWidget(self.log_display) + + # Add init log text matching + self.append_log(f"[{QDateTime.currentDateTime().toString('yyyy-MM-dd hh:mm:ss')}] Log initialized", "info") + + self.setLayout(layout) + self.show() # Show init text + + def append_log(self, message, log_level="info"): + """Append a message to the log display with specified color.""" + if log_level == "error": + color = "#FF0000" # Red for errors + elif log_level == "info": + color = "#A6A6A6" # Black for info + elif log_level == "success": + color = "#008000" # Green for success + + formatted_message = f"{message}" + self.log_display.append(formatted_message) + self.log_display.ensureCursorVisible() + + def toggle_log_dock(self): + """Toggle the visibility of the log dock.""" + self.is_visible = not self.is_visible + if self.is_visible: + self.show() + self.adjust_size() + self.move(0, self.parent().height() - self.height()) + else: + self.hide() + + def adjust_size(self): + """Adjust the size of the log dock based on input and output dock states.""" + parent = self.parent() + if not parent: + return + + # Get the current tab's input and output dock states + current_tab_index = parent.tab_bar.currentIndex() + if current_tab_index < 0 or current_tab_index >= len(parent.tab_widget_content): + return + + input_active = parent.tab_widget_content[current_tab_index][3] + output_active = parent.tab_widget_content[current_tab_index][4] + + # Calculate available width + parent_width = parent.width() + input_dock_width = parent.tab_widget_content[current_tab_index][1].width() if input_active else 0 + output_dock_width = parent.tab_widget_content[current_tab_index][2].width() if output_active else 0 + available_width = parent_width - input_dock_width - output_dock_width + + # Set log dock size + default_height = 150 # Fixed height for log dock + self.setFixedSize(available_width, default_height) + + # Update position if visible + if self.is_visible: + self.move(0, parent.height() - default_height) \ No newline at end of file diff --git a/src/osdagbridge/desktop/legacy_src/src/osbridge/ui/output_dock.py b/src/osdagbridge/desktop/ui/docks/output_dock.py similarity index 79% rename from src/osdagbridge/desktop/legacy_src/src/osbridge/ui/output_dock.py rename to src/osdagbridge/desktop/ui/docks/output_dock.py index ca3e16aa..34fc3cf1 100644 --- a/src/osdagbridge/desktop/legacy_src/src/osbridge/ui/output_dock.py +++ b/src/osdagbridge/desktop/ui/docks/output_dock.py @@ -1,13 +1,86 @@ from PySide6.QtWidgets import ( QWidget, QVBoxLayout, QHBoxLayout, QLabel, QSizePolicy, - QPushButton, QGroupBox, QCheckBox, QScrollArea, QFrame, + QPushButton, QGroupBox, QCheckBox, QScrollArea, QFrame, QComboBox ) from PySide6.QtCore import Qt, QSize from PySide6.QtGui import QIcon -from osbridge.ui.custom_buttons import DockCustomButton -from osbridge.ui.input_dock import NoScrollComboBox, apply_field_style +from osdagbridge.desktop.ui.utils.custom_buttons import DockCustomButton +class NoScrollComboBox(QComboBox): + def wheelEvent(self, event): + event.ignore() # Prevent changing selection on scroll + +def apply_field_style(widget): + widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) + widget.setMinimumHeight(28) + if isinstance(widget, QComboBox): + style = """ + QComboBox{ + padding: 1px 7px; + border: 1px solid black; + border-radius: 5px; + background-color: white; + color: black; + } + QComboBox::drop-down{ + subcontrol-origin: padding; + subcontrol-position: top right; + border-left: 0px; + } + QComboBox::down-arrow{ + image: url(:/vectors/arrow_down_light.svg); + width: 20px; + height: 20px; + margin-right: 8px; + } + QComboBox::down-arrow:on { + image: url(:/vectors/arrow_up_light.svg); + width: 20px; + height: 20px; + margin-right: 8px; + } + QComboBox QAbstractItemView{ + background-color: white; + border: 1px solid black; + outline: none; + } + QComboBox QAbstractItemView::item{ + color: black; + background-color: white; + border: none; + border: 1px solid white; + border-radius: 0; + padding: 2px; + } + QComboBox QAbstractItemView::item:hover{ + border: 1px solid #90AF13; + background-color: #90AF13; + color: black; + } + QComboBox QAbstractItemView::item:selected{ + background-color: #90AF13; + color: black; + border: 1px solid #90AF13; + } + QComboBox QAbstractItemView::item:selected:hover{ + background-color: #90AF13; + color: black; + border: 1px solid #94b816; + } + """ + widget.setStyleSheet(style) + elif isinstance(widget, QLineEdit): + widget.setStyleSheet(""" + QLineEdit { + padding: 1px 7px; + border: 1px solid #070707; + border-radius: 6px; + background-color: white; + color: #000000; + font-weight: normal; + } + """) class OutputDock(QWidget): """Output dock with collapsible design controls and scrollable layout.""" @@ -148,17 +221,14 @@ def init_ui(self): col1 = QVBoxLayout() for text in ["Fx", "Mx", "Dx"]: cb = QCheckBox(text) - cb.setStyleSheet("font-size: 10px; color: #333;") col1.addWidget(cb) col2 = QVBoxLayout() for text in ["Fy", "My", "Dy"]: cb = QCheckBox(text) - cb.setStyleSheet("font-size: 10px; color: #333;") col2.addWidget(cb) col3 = QVBoxLayout() for text in ["Fz", "Mz", "Dz"]: cb = QCheckBox(text) - cb.setStyleSheet("font-size: 10px; color: #333;") col3.addWidget(cb) forces_grid.addLayout(col1) forces_grid.addLayout(col2) @@ -173,13 +243,11 @@ def init_ui(self): display_row.setSpacing(12) for text in ["Max", "Min"]: cb = QCheckBox(text) - cb.setStyleSheet("font-size: 10px; color: #333;") display_row.addWidget(cb) display_row.addStretch() results_layout.addLayout(display_row) utilization_check = QCheckBox("Controlling Utilization Ratio") - utilization_check.setStyleSheet("font-size: 10px; color: #333;") results_layout.addWidget(utilization_check) scroll_layout.addWidget(results_group) @@ -233,13 +301,13 @@ def init_ui(self): struct_header.addStretch() # Collapse/expand toggle using SVG icon - toggle_btn = QPushButton() - toggle_btn.setCursor(Qt.CursorShape.PointingHandCursor) - toggle_btn.setCheckable(True) - toggle_btn.setChecked(True) - toggle_btn.setIcon(QIcon(":/vectors/arrow_up_light.svg")) - toggle_btn.setIconSize(QSize(20, 20)) - toggle_btn.setStyleSheet(""" + super_toggle_btn = QPushButton() + super_toggle_btn.setCursor(Qt.CursorShape.PointingHandCursor) + super_toggle_btn.setCheckable(True) + super_toggle_btn.setChecked(True) + super_toggle_btn.setIcon(QIcon(":/vectors/arrow_up_light.svg")) + super_toggle_btn.setIconSize(QSize(20, 20)) + super_toggle_btn.setStyleSheet(""" QPushButton { background: transparent; border: none; @@ -252,17 +320,17 @@ def init_ui(self): background: transparent; } """) - struct_header.addWidget(toggle_btn) + struct_header.addWidget(super_toggle_btn) structure_layout.addLayout(struct_header) # body widget that contains everything inside the Superstructure and can be hidden - structure_body = QFrame() - structure_body.setFrameShape(QFrame.NoFrame) - structure_body_layout = QVBoxLayout(structure_body) - structure_body_layout.setContentsMargins(0, 0, 0, 0) - structure_body_layout.setSpacing(10) - structure_body.setVisible(True) + super_body = QFrame() + super_body.setFrameShape(QFrame.NoFrame) + super_body_layout = QVBoxLayout(super_body) + super_body_layout.setContentsMargins(0, 0, 0, 0) + super_body_layout.setSpacing(10) + super_body.setVisible(True) # Additional Geometry (inside Superstructure body) add_geo_row = QHBoxLayout() @@ -296,7 +364,7 @@ def init_ui(self): """) modify_geo_btn.clicked.connect(self.show_additional_inputs) add_geo_row.addWidget(modify_geo_btn, 1) - structure_body_layout.addLayout(add_geo_row) + super_body_layout.addLayout(add_geo_row) #--------------------------------------------- @@ -332,18 +400,18 @@ def init_ui(self): """) modify_geo_btn.clicked.connect(self.show_additional_inputs) add_geo_row.addWidget(modify_geo_btn, 1) - structure_body_layout.addLayout(add_geo_row) + super_body_layout.addLayout(add_geo_row) # Add body to structure layout - structure_layout.addWidget(structure_body) + structure_layout.addWidget(super_body) - def _toggle_structure(checked): + def _toggle_superstructure(checked, body=super_body, btn=super_toggle_btn): # checked True means show body (open) - structure_body.setVisible(checked) - toggle_btn.setIcon(QIcon(":/vectors/arrow_up_light.svg" if checked else ":/vectors/arrow_down_light.svg")) + body.setVisible(checked) + btn.setIcon(QIcon(":/vectors/arrow_up_light.svg" if checked else ":/vectors/arrow_down_light.svg")) - toggle_btn.toggled.connect(_toggle_structure) + super_toggle_btn.toggled.connect(_toggle_superstructure) structure_group.setLayout(structure_layout) design_layout.addWidget(structure_group) diff --git a/src/osdagbridge/desktop/ui/main_window.ui b/src/osdagbridge/desktop/ui/main_window.ui deleted file mode 100644 index b52aea53..00000000 --- a/src/osdagbridge/desktop/ui/main_window.ui +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/src/osdagbridge/desktop/ui/template_page.py b/src/osdagbridge/desktop/ui/template_page.py new file mode 100644 index 00000000..0bb59ec4 --- /dev/null +++ b/src/osdagbridge/desktop/ui/template_page.py @@ -0,0 +1,128 @@ +import sys +from PySide6.QtWidgets import ( + QApplication, QWidget, QVBoxLayout, QHBoxLayout, QLabel, + QMenuBar, QSplitter, QSizePolicy, QPushButton, QScrollArea, QFrame, +) +from PySide6.QtCore import Qt +from PySide6.QtCore import Qt, QFile, QTextStream +from PySide6.QtGui import QIcon + +from osdagbridge.desktop.ui.docks.input_dock import InputDock +from osdagbridge.desktop.ui.docks.output_dock import OutputDock +from osdagbridge.desktop.ui.docks.log_dock import LogDock + +from osdagbridge.core.bridge_types.plate_girder.ui_fields import FrontendData +from osdagbridge.core.utils.common import * + +class DummyCADWidget(QWidget): + """Placeholder for CAD widget""" + + def __init__(self): + super().__init__() + layout = QVBoxLayout(self) + label = QLabel("CAD Window\n(Placeholder)") + label.setAlignment(Qt.AlignCenter) + label.setStyleSheet( + """ + QLabel { + background-color: #f0f0f0; + border: 1px solid #999; + padding: 40px; + font-size: 18px; + color: #666; + } + """ + ) + layout.addWidget(label) + +class CustomWindow(QWidget): + def __init__(self, title: str, backend: object, parent=None): + super().__init__() + self.parent = parent + self.backend = backend() + + self.setWindowTitle(title) + self.setStyleSheet( + """ + QWidget { + background-color: #ffffff; + margin: 0px; + padding: 0px; + } + QMenuBar { + background-color: #F4F4F4; + color: #000000; + padding: 0px; + } + QMenuBar::item { + padding: 5px 10px; + background: transparent; + } + QMenuBar::item:selected { + background: #FFFFFF; + } + """ + ) + + self.init_ui() + + def init_ui(self): + main_v_layout = QVBoxLayout(self) + main_v_layout.setContentsMargins(0, 0, 0, 0) + main_v_layout.setSpacing(0) + + self.menu_bar = QMenuBar(self) + self.menu_bar.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed) + self.menu_bar.setFixedHeight(28) + self.menu_bar.setContentsMargins(0, 0, 0, 0) + self.menu_bar.addMenu("File") + self.menu_bar.addMenu("Edit") + self.menu_bar.addMenu("Graphics") + self.menu_bar.addMenu("Help") + main_v_layout.addWidget(self.menu_bar) + + body_widget = QWidget() + body_layout = QHBoxLayout(body_widget) + body_layout.setContentsMargins(0, 0, 0, 0) + body_layout.setSpacing(0) + + # Main horizontal splitter + main_splitter = QSplitter(Qt.Horizontal, body_widget) + + # Input dock + input_dock = InputDock(backend=self.backend, parent=self) + input_dock.setMinimumWidth(380) + input_dock.setMaximumWidth(450) + main_splitter.addWidget(input_dock) + + # CAD widget + cad_widget = DummyCADWidget() + main_splitter.addWidget(cad_widget) + + # Output dock + output_dock = OutputDock() + output_dock.setMinimumWidth(380) + output_dock.setMaximumWidth(450) + main_splitter.addWidget(output_dock) + + body_layout.addWidget(main_splitter) + + # Set stretch factors for main splitter + main_splitter.setStretchFactor(0, 0) # Input dock - fixed + main_splitter.setStretchFactor(1, 1) # Central area - expandable + main_splitter.setStretchFactor(2, 0) # Output dock - fixed + + # Set initial sizes + input_dock_width = 350 + output_dock_width = 280 + total_width = self.width() if self.width() > 0 else 1200 + central_width = max(500, total_width - input_dock_width - output_dock_width) + main_splitter.setSizes([input_dock_width, central_width, output_dock_width]) + + main_v_layout.addWidget(body_widget) + + # Store references + self.main_splitter = main_splitter + self.input_dock = input_dock + self.output_dock = output_dock + self.cad_widget = cad_widget diff --git a/src/osdagbridge/desktop/legacy_src/src/osbridge/ui/custom_buttons.py b/src/osdagbridge/desktop/ui/utils/custom_buttons.py similarity index 100% rename from src/osdagbridge/desktop/legacy_src/src/osbridge/ui/custom_buttons.py rename to src/osdagbridge/desktop/ui/utils/custom_buttons.py From 44013eb4c2907d7c9e48a20b8eaf6ab31d6792b9 Mon Sep 17 00:00:00 2001 From: mhsuhail00 Date: Fri, 12 Dec 2025 22:56:21 +0530 Subject: [PATCH 46/59] -Separated project_detail dialog -Fixed Lock button --- src/osdagbridge/desktop/__main__.py | 1 + .../desktop/resources/resources.qrc | 1 + .../desktop/resources/resources_rc.py | 377 ++++-- .../desktop/resources/themes/lightstyle.qss | 96 +- .../desktop/resources/vectors/Osdag_logo.svg | 15 + .../ui/{ => dialogs}/additional_inputs.py | 1 - .../desktop/ui/dialogs/project_location.py | 387 +++++++ .../desktop/ui/docks/input_dock.py | 1012 +++-------------- .../desktop/ui/utils/custom_titlebar.py | 168 +++ 9 files changed, 1138 insertions(+), 920 deletions(-) create mode 100644 src/osdagbridge/desktop/resources/vectors/Osdag_logo.svg rename src/osdagbridge/desktop/ui/{ => dialogs}/additional_inputs.py (99%) create mode 100644 src/osdagbridge/desktop/ui/dialogs/project_location.py create mode 100644 src/osdagbridge/desktop/ui/utils/custom_titlebar.py diff --git a/src/osdagbridge/desktop/__main__.py b/src/osdagbridge/desktop/__main__.py index 067302af..1149e7ff 100644 --- a/src/osdagbridge/desktop/__main__.py +++ b/src/osdagbridge/desktop/__main__.py @@ -1,6 +1,7 @@ import sys from PySide6.QtWidgets import QApplication from PySide6.QtCore import QFile, QTextStream +from osdagbridge.desktop.resources import resources_rc # Import template_page from osdagbridge.desktop.ui.template_page import CustomWindow diff --git a/src/osdagbridge/desktop/resources/resources.qrc b/src/osdagbridge/desktop/resources/resources.qrc index c847e516..db511f3e 100644 --- a/src/osdagbridge/desktop/resources/resources.qrc +++ b/src/osdagbridge/desktop/resources/resources.qrc @@ -19,5 +19,6 @@ vectors/design_report.svg vectors/lock_open.svg vectors/lock_close.svg + vectors/Osdag_logo.svg diff --git a/src/osdagbridge/desktop/resources/resources_rc.py b/src/osdagbridge/desktop/resources/resources_rc.py index ed54a87e..845efa00 100644 --- a/src/osdagbridge/desktop/resources/resources_rc.py +++ b/src/osdagbridge/desktop/resources/resources_rc.py @@ -6,55 +6,54 @@ from PySide6 import QtCore qt_resource_data = b"\ -\x00\x00\x02\xec\ +\x00\x00\x02\xe0\ \x00\ -\x00\x0a\x95x\xda\xb5V]O\xdb0\x14}\xaf\xd4\xff\ -p\x05/\x0c)\xf4\x0b\x0a\x0b\xdaC[JW\xa9\x1b\ -\xebR\x98\xd8\x0br\x127\xb1p\xec\xccq(\x0c\xf1\ -\xdf\xe7\x848M\xdb,\xb4hs^\xd2\x1b\xfb\x9es\ -\xef=9i\xe3\x10>\xed\xb4\xea5\x00\xb8\xb2.z\ -#\x18]\x8f\xc1\x9a\xddN\x86\xd6\xe7\xe1p\x06\x06\x5c\ -}\x1f\xf5\xbe\x8e\x7f\x0e/\xa0\x7f\x0b\xd6\xb7\xe1`|\ -9\x1e\x8cg\xb7\xe9\x99\xdd`\xe0\xb0Q\xaf\xd5k\x8d\ -w\xd1k\x1d\xc1hr\xd5\xefM2zp0\xc1(\ -\x92`\x85\xd8!s\xe2|x/\xa1CxN\x8f\xc2\ -\x9c3i\xccQ@\xe8\x93\x09{\xd7v\xccd\x0c\x16\ -b\xd1\xdey\xbd\xf6R\xafM\xbf \xc2~\x10\xe6\xf2\ -\x85>b#\xe7\xde\x13v\xee\ -m\xfe\x08\x96|R\x09\x92QN\xd3X_\xc5\x9e7\ -\xd9/I\xea\xcc\x9d\x8e.>\x0a\x91\x93\x16\xdf\xd5]\ -\xcfS\x99\xa6\x9a8q\x90\xe4Bg]\x10W\xfa*\ -c7\xcf\xe8c\xe2\xf9r%T\xd2\xefN\xba\xca[\ -\xd8Y\x1e,\xd1T\xba\xfeN\xcc\xf4\xf9\x03\xce\xe9\x95\ - \x9f\xa4\xab\x22\x81\x93\xc4\xb0[\xa5k\xcda\x9b\xe2\ -H\x80<\xd5\xf3X\xd0\x03\xb3\xf1\x80\x1d\x05\x1152\ -\x8c\xa3\xe8\xc1\xfb\xb09\xce\xe9\x80\x076\xef\xaf\xcdS\ -\xc7\x9e\xd7D\x9a\x00\x9fV\xf4\xda\xa6\xaa\x82\xf2N\x9f\ -Ttz\xe1\x13\x89WUR\xcc\x14\x10f\xe8Q\xb7\ -\xcf\x96R\xc9X\x9a\xa6\xab\xd4o(\x7f`\x9ap\x14\ -\xdb\x8e\xd2\xa0\xe0\xd4\xe0\x82\xa4\xefzV\xc3\xf9\xc6\x86\ -\x90GD\x12\xae\xb6H\x1e\x82H`V+\xa0x.\ -\x0b\xc6P\xc4U\x90\x06\x12b\xe9L\xa5\x13Hw\xdc\ -%\x9b\xefh\x92^\x8f\xa2 \xeavsC\xd4\x85\xd0\ -\xab_\x19\xe2\xf5\xc1Y%\x13\x93\xb3-\xc8\xc4\xe1\x7f\ -\xa2\x02\xd3\x9e\x1dI\x81\x1c9\x968\xb8!\xb8\xc2\xb4\ -\x8bc\xaf\xd6\x12\x8f%%L\xd5\xc28\xc3o\x22\xaa\ -7L\xddi\xdc\x12AmG%\xc3*g\xb7y`\ -i\xcb%\xd6\xbe%\xe77\x1de\xe5\xdbQb\x17+\ -\xcf\xd7*\xdf\x8a@\x84\xa9\x12J\xa5%Ua\xbcE\ -{'\x0ek\xdd\xf87L\x8e\xed\xb3V\xb7\xc4\x07'\ -J_C\x97\xc8\xa2\x0f\xe6\xb1]}p\xbfy\x9a\x5c\ -\xe5\xfa\xe8\xee\xe6\x84\xab\xff'\xd2O\xeb\x22{/\x19\ -\x17\x01\xa2\x95.\xf9\x07s\xef\x15`\ +\x00\x0b\xd8x\xda\xd5U\xdfO\xa3@\x10~7\xf1\x7f\ +\x98\xa4/zIm+\xb5\x171\xf7P*\xf6\x9a\xa0\ +U\xc13\xde\xdb\x02[\xd8\xb8e9\xd8\xfe\xf0\xcc\xfd\ +\xef\xb7P\xa0\x80\x14\xb4>\xb9\xfb\x02\xc3\xce7\xdf|\ +;3t\xbe\xc1\x8f\x0f\xad\xc3\x03\x00\x98\xea\x97\xc31\ +\x8c\x1f&\xa0\x1bO\x9a\xaa\xffTU\x03\xda0\xbd\x1f\ +\x0fo&\xbf\xd5KP\x9e@\xbfUG\x93\xab\xc9h\ +b<\xc5>\x1f\x0b\x03\xdf:\x87\x07\x87\x07\x9d\xbd\xe8\ +\xf5N`\xacM\x95\xa1\x96\xd0\x83#\x0d\xa3\x90\x83\xee\ +c\x8b\xcc\x88u\xbc/\xa1\xbbkD\xbcG\xe2\xd9l\ +\x05\xaf1\x08\x98\xc8zv\x02\xb6\xf0\xec\xb6\xc5(\x0b\ +dh\xcd\xfa\xd1\xbeH\xbe\xb3\xc0\xc6\xc2\xda\xf3\xd7\x10\ +2Jlh\x9dw\xd1\xac'%\xdf\xe7(p\x88'\ +C\xd7_'\x16\x1f\xd96\xf1\x9c\xd4\xf4/\xd2\xe1\xce\ +`\x8c\x1a\xc4\xaf\x89z\x15\xaf\x04#5v\xe3UG\ +ex\x95Q\xc9\x02\x9f\x8a\x03\xa7\x19\x9f\x19\xf3x;\ +$\x7f\xb1\xf0\xdcZ7`\xed\x00\xd9d\x11\xe6\xe9\xff\ +\xf1\x03\xe6\xe3\x80\xbf\xb4\x11%\x8e7\xc7\x1e\x97a\x18\ +=\xfe\x1a\x89g\x1clr\xba\xd3}J\xb8x\x95e\ +\x17y6\xc55\xa9]v\xa3\x9dj!jbL\x99\ +\x89(\x8c\x5cl=\x9bl\x0d:\x7f\x11\x00\xf1\x0d\xc5\ +6E\xd8^\xdf\xb2\xdf\x92L\x91%)M>\xf4\x91\ +\x15'?HU\xcf\xa0dY\xdc8\xb1\x10gA\x8a\ +\xba\x226w\x05\xe2 Ct1q\x5c^0U\xe8\ +-\xc5\xabZBi\xebXQS\xf1\xdaMLv\xd9\ +\x12g\xf4*\x22\x9f\xc5\xab\x06\xc0\x8al\xd8\xae\xab\xeb\ +\x94\xc3{\x92#s\xe4\x08\xcd\x17\x01=\x92;Kl\ +\x89\x10a'\x89q\x12.\x9d\xe3\xdcu\xee\xd1\xe2\x92\ +hq\xf5F\xbd\x17=\xae<\x18\xc6\xf4&ku\x05\ +\x85\x184\xbc\xc4t\xff6\xbf]\x84\xae\xb2\xe0\x9cy\ +\xbb\xe5X\xb9\x84\xe3b9\x99T\x9c\xcaw\xcd*)\ +\x0a\x93Q\xfb\x02*o\xfd\xac\xa6\x5c\xf2xYs\x0a\ +\x07\xe8\xf53/\x8e\xd7|\xd3i2X\xb9\xfe*d\ +Q\xaa\x8e\xb7W[\x98\x03Ms\xa2$\xc0\x9b`~\ +\x80\xc3p[I\x15\xe2\xd4\xab\xb9[\x88M\xa8Gb\ +;\x98\xb7F\x8b\x90\xb3\xb9A8\xc5\x0a\x12\xc9A\xf3\ +@\x8ej_C&\xa6\xad\xd8-~,\xd1,\x8e\xcc\ +\xd2,.\xe2\xcb\xc0\x03\xe4\x89\xc1\x11\x08\xdd\x0b\xe8\x1a\ +sX\x01|\xb7S>t\xa1\xc1\xf2\x83\xab_\xfa\x13\ +l\x84n]\x13\x8f\xcc\xc5\x89\xa6R\xdd\x1d\xb1\xf2\xff\ +\xd0m\x1e\xf2yr\x83\xfa\xe3\x0d\xac\x1bKs\xd6\x8b\ +\xf6;\x80JeW\x01\x85\x06\xd1\xae\x84B\xeb{,\ +\xca)\xf8jZ\x96x\x7fF\xcd2\xd4g\xf4\x1cQ\ +\x16~1)s\x94\x1bU\xc4\xdf\xfb\x96d\xed\xea\xde\ +Z\xe8fU\xad\xaet~j&\xf3$\x99u\x0a\x13\ +\xces\x8dx\xf8=C\x5c8\xfe\x07\x97\xd7n\xe4\ \x00\x00\x00\xb2\ <\ svg xmlns=\x22http:\ @@ -69,6 +68,236 @@ 1 367-367 57 57-\ 424 424Z\x22/>\ +\x00\x00\x0e;\ +<\ +svg width=\x22149\x22 \ +height=\x22141\x22 vie\ +wBox=\x220 0 149 14\ +1\x22 fill=\x22none\x22 x\ +mlns=\x22http://www\ +.w3.org/2000/svg\ +\x22>\x0d\x0a\x0d\x0a<\ +path d=\x22M130.212\ + 67.3074L115.887\ + 51.8725L130.121\ + 36.1514L130.212\ + 67.3074Z\x22 fill=\ +\x22black\x22/>\x0d\x0a\x0d\x0a\x0d\x0a\x0d\x0a\x0d\x0a\x0d\x0a\x0d\x0a\x0d\x0a\x0d\x0a\ +\x0d\x0a\x0d\x0a\ +\x0d\x0a\x0d\x0a\ \x00\x00\x02\xfe\ <\ svg xmlns=\x22http:\ @@ -569,6 +798,10 @@ \x00c\ \x00h\x00e\x00c\x00k\x00e\x00d\x00.\x00s\x00v\x00g\ \x00\x0e\ +\x06\xb1\x9b\x07\ +\x00O\ +\x00s\x00d\x00a\x00g\x00_\x00l\x00o\x00g\x00o\x00.\x00s\x00v\x00g\ +\x00\x0e\ \x0d\xd6\xeag\ \x00l\ \x00o\x00c\x00k\x00_\x00c\x00l\x00o\x00s\x00e\x00.\x00s\x00v\x00g\ @@ -614,32 +847,34 @@ qt_resource_struct = b"\ \x00\x00\x00\x00\x00\x02\x00\x00\x00\x02\x00\x00\x00\x01\ \x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x14\x00\x02\x00\x00\x00\x01\x00\x00\x00\x0d\ +\x00\x00\x00\x14\x00\x02\x00\x00\x00\x01\x00\x00\x00\x0e\ \x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x02\x00\x00\x00\x0a\x00\x00\x00\x03\ +\x00\x00\x00\x00\x00\x02\x00\x00\x00\x0b\x00\x00\x00\x03\ \x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\xf6\x00\x00\x00\x00\x00\x01\x00\x00\x11\x07\ -\x00\x00\x01\x9b\x03\xb6\xa6\x8f\ -\x00\x00\x00H\x00\x00\x00\x00\x00\x01\x00\x00\x02\xf0\ -\x00\x00\x01\x9b\x12,Gf\ -\x00\x00\x01\x16\x00\x00\x00\x00\x00\x01\x00\x00\x13\xe9\ -\x00\x00\x01\x9b\x03\xb6\xa6\x8b\ -\x00\x00\x01D\x00\x00\x00\x00\x00\x01\x00\x00\x16\x1e\ -\x00\x00\x01\x9b\x03\xb6\xa6\x8b\ -\x00\x00\x01l\x00\x00\x00\x00\x00\x01\x00\x00\x1bq\ -\x00\x00\x01\x9b\x03\xb6\xa6\x8d\ -\x00\x00\x01\x94\x00\x00\x00\x00\x00\x01\x00\x00\x1e0\ -\x00\x00\x01\x9b\x03\xb6\xa6\x8f\ -\x00\x00\x00\xcc\x00\x00\x00\x00\x00\x01\x00\x00\x0b\xb4\ -\x00\x00\x01\x9b\x03\xb6\xa6\x8c\ -\x00\x00\x00\x86\x00\x00\x00\x00\x00\x01\x00\x00\x06\xa8\ -\x00\x00\x01\x9b\x03\xb6\xa6\x89\ -\x00\x00\x00d\x00\x00\x00\x00\x00\x01\x00\x00\x03\xa6\ -\x00\x00\x01\x9b\x03\xb6\xa6\x8e\ -\x00\x00\x00\xb2\x00\x00\x00\x00\x00\x01\x00\x00\x08\xdd\ -\x00\x00\x01\x9b\x03\xb6\xa6\x8c\ +\x00\x00\x01\x18\x00\x00\x00\x00\x00\x01\x00\x00\x1f:\ +\x00\x00\x01\x9b\x13E\x17;\ +\x00\x00\x00H\x00\x00\x00\x00\x00\x01\x00\x00\x02\xe4\ +\x00\x00\x01\x9b\x13E\x179\ +\x00\x00\x018\x00\x00\x00\x00\x00\x01\x00\x00\x22\x1c\ +\x00\x00\x01\x9b\x13E\x179\ +\x00\x00\x01f\x00\x00\x00\x00\x00\x01\x00\x00$Q\ +\x00\x00\x01\x9b\x13E\x179\ +\x00\x00\x00d\x00\x00\x00\x00\x00\x01\x00\x00\x03\x9a\ +\x00\x00\x01\x9a\xfc\x9f}\x0c\ +\x00\x00\x01\x8e\x00\x00\x00\x00\x00\x01\x00\x00)\xa4\ +\x00\x00\x01\x9b\x13E\x179\ +\x00\x00\x01\xb6\x00\x00\x00\x00\x00\x01\x00\x00,c\ +\x00\x00\x01\x9b\x13E\x17;\ +\x00\x00\x00\xee\x00\x00\x00\x00\x00\x01\x00\x00\x19\xe7\ +\x00\x00\x01\x9b\x13E\x179\ +\x00\x00\x00\xa8\x00\x00\x00\x00\x00\x01\x00\x00\x14\xdb\ +\x00\x00\x01\x9b\x13E\x179\ +\x00\x00\x00\x86\x00\x00\x00\x00\x00\x01\x00\x00\x11\xd9\ +\x00\x00\x01\x9b\x13E\x179\ +\x00\x00\x00\xd4\x00\x00\x00\x00\x00\x01\x00\x00\x17\x10\ +\x00\x00\x01\x9b\x13E\x179\ \x00\x00\x00&\x00\x01\x00\x00\x00\x01\x00\x00\x00\x00\ -\x00\x00\x01\x9b\x12>\x9d\xfb\ +\x00\x00\x01\x9b\x13e\xa4S\ " def qInitResources(): diff --git a/src/osdagbridge/desktop/resources/themes/lightstyle.qss b/src/osdagbridge/desktop/resources/themes/lightstyle.qss index 7c86c316..71146c11 100644 --- a/src/osdagbridge/desktop/resources/themes/lightstyle.qss +++ b/src/osdagbridge/desktop/resources/themes/lightstyle.qss @@ -45,4 +45,98 @@ QCheckBox::indicator:checked { background-color: #ffffff; border: 1px solid #333333; image: url(:/vectors/checked.svg); -} \ No newline at end of file +} + +/* ============================================== + 3. GENERAL BUTTON STYLES (Base Level) + ============================================== */ +QPushButton { + background-color: white; + color: black; + font-weight: bold; + border-radius: 5px; + border: 1px solid black; + padding: 5px 14px; + text-align: center; +} + +QPushButton:hover { + background-color: #90AF13; + border: 1px solid #90AF13; + color: white; +} + +QPushButton:pressed { + color: black; + background-color: white; + border: 1px solid black; +} + +QWidget#CustomTitleBar { + background-color: #f4f4f4; +} +QLabel#TitleLabel { + color: #000000; + padding: 0px; + background: transparent; +} +QLabel#LogoLabel { + background: transparent; + color: #ffffff; + font-size: 14px; +} + +QToolButton#MinimizeButton { + background-color: transparent; + color: #000000; + border: 0px; + border-radius: 0px; + font-size: 16px; + border-radius: 0px; +} + +QToolButton#MinimizeButton:hover { + background-color: #f1f1f1; +} + +QToolButton#MinimizeButton:pressed { + background-color: #a6a6a6; +} + +QToolButton#MaxRestoreButton { + background-color: transparent; + color: #000000; + border: 0px; + border-radius: 0px; + font-size: 16px; + border-radius: 0px; +} + +QToolButton#MaxRestoreButton:hover { + background-color: #f1f1f1; +} + +QToolButton#MaxRestoreButton:pressed { + background-color: #a6a6a6; +} + +QToolButton#CloseButton { + background-color: transparent; + color: #000000; + border: 0px; + border-radius: 0px; + font-size: 16px; + border-radius: 0px; +} + +QToolButton#CloseButton:hover { + background-color: #e74c3c; + color: #ffffff; +} + +QToolButton#CloseButton:pressed { + background-color: #c0392b; +} +QWidget#BottomLine { + background-color: #90AF13; +} diff --git a/src/osdagbridge/desktop/resources/vectors/Osdag_logo.svg b/src/osdagbridge/desktop/resources/vectors/Osdag_logo.svg new file mode 100644 index 00000000..1ee3a590 --- /dev/null +++ b/src/osdagbridge/desktop/resources/vectors/Osdag_logo.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/src/osdagbridge/desktop/ui/additional_inputs.py b/src/osdagbridge/desktop/ui/dialogs/additional_inputs.py similarity index 99% rename from src/osdagbridge/desktop/ui/additional_inputs.py rename to src/osdagbridge/desktop/ui/dialogs/additional_inputs.py index f1503c81..0c78ef78 100644 --- a/src/osdagbridge/desktop/ui/additional_inputs.py +++ b/src/osdagbridge/desktop/ui/dialogs/additional_inputs.py @@ -15,7 +15,6 @@ from PySide6.QtGui import QDoubleValidator, QIntValidator from osdagbridge.core.utils.common import * -import osdagbridge.desktop.resources.resources_rc def get_combobox_style(): """Return the common stylesheet for dropdowns with the SVG icon from resources.""" diff --git a/src/osdagbridge/desktop/ui/dialogs/project_location.py b/src/osdagbridge/desktop/ui/dialogs/project_location.py new file mode 100644 index 00000000..c263e21f --- /dev/null +++ b/src/osdagbridge/desktop/ui/dialogs/project_location.py @@ -0,0 +1,387 @@ +from PySide6.QtWidgets import ( + QDialog, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QWidget, + QCheckBox, QFrame, QPushButton, QComboBox, QSizePolicy, QSizeGrip +) +from PySide6.QtCore import Qt +from osdagbridge.desktop.ui.utils.custom_titlebar import CustomTitleBar + +class NoScrollComboBox(QComboBox): + def wheelEvent(self, event): + event.ignore() # Prevent changing selection on scroll + +def apply_field_style(widget): + widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) + widget.setMinimumHeight(28) + + if isinstance(widget, QComboBox): + style = """ + QComboBox{ + padding: 1px 7px; + border: 1px solid black; + border-radius: 5px; + background-color: white; + color: black; + } + QComboBox::drop-down{ + subcontrol-origin: padding; + subcontrol-position: top right; + border-left: 0px; + } + QComboBox::down-arrow{ + image: url(:/vectors/arrow_down_light.svg); + width: 20px; + height: 20px; + margin-right: 8px; + } + QComboBox::down-arrow:on { + image: url(:/vectors/arrow_up_light.svg); + width: 20px; + height: 20px; + margin-right: 8px; + } + QComboBox QAbstractItemView{ + background-color: white; + border: 1px solid black; + outline: none; + } + QComboBox QAbstractItemView::item{ + color: black; + background-color: white; + border: none; + border: 1px solid white; + border-radius: 0; + padding: 2px; + } + QComboBox QAbstractItemView::item:hover{ + border: 1px solid #90AF13; + background-color: #90AF13; + color: black; + } + QComboBox QAbstractItemView::item:selected{ + background-color: #90AF13; + color: black; + border: 1px solid #90AF13; + } + QComboBox QAbstractItemView::item:selected:hover{ + background-color: #90AF13; + color: black; + border: 1px solid #94b816; + } + """ + widget.setStyleSheet(style) + elif isinstance(widget, QLineEdit): + widget.setStyleSheet(""" + QLineEdit { + padding: 1px 7px; + border: 1px solid #070707; + border-radius: 6px; + background-color: white; + color: #000000; + font-weight: normal; + } + """) + + +class ProjectLocationDialog(QDialog): + """Dialog for selecting project location with multiple input methods""" + + STATE_DISTRICTS = { + "Select State": ["Select District"], + "Delhi": ["Central Delhi", "East Delhi", "New Delhi", "North Delhi", "North East Delhi", + "North West Delhi", "South Delhi", "South East Delhi", "South West Delhi", "West Delhi"], + "Maharashtra": ["Mumbai", "Pune", "Nagpur", "Thane", "Nashik", "Aurangabad", "Solapur", + "Amravati", "Kolhapur", "Raigad", "Satara", "Sangli"], + "Karnataka": ["Bangalore", "Mysore", "Hubli", "Belgaum", "Mangalore", "Gulbarga", + "Bellary", "Bijapur", "Shimoga", "Tumkur", "Davangere"], + "Tamil Nadu": ["Chennai", "Coimbatore", "Madurai", "Tiruchirappalli", "Salem", "Tirunelveli", + "Tiruppur", "Erode", "Vellore", "Thoothukudi", "Dindigul"], + "West Bengal": ["Kolkata", "Howrah", "Darjeeling", "Siliguri", "Asansol", "Durgapur", + "Bardhaman", "Malda", "Jalpaiguri", "Murshidabad", "Nadia"] + } + + def __init__(self, parent=None): + super().__init__(parent) + self.setMinimumWidth(850) + self.setMinimumHeight(650) + self.setObjectName("project_location_dialog") + self.setStyleSheet(""" + QDialog#project_location_dialog { + background-color: #FFFFFF; + border: 1px solid #90AF13; + } + """) + + self._setup_ui() + self._connect_signals() + + def setupWrapper(self): + self.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowSystemMenuHint) + + main_layout = QVBoxLayout(self) + main_layout.setContentsMargins(1, 1, 1, 1) + main_layout.setSpacing(0) + + self.title_bar = CustomTitleBar() + self.title_bar.setTitle("Project Location") + main_layout.addWidget(self.title_bar) + + self.content_widget = QWidget(self) + main_layout.addWidget(self.content_widget, 1) + + size_grip = QSizeGrip(self) + size_grip.setFixedSize(16, 16) + + overlay = QHBoxLayout() + overlay.setContentsMargins(0, 0, 4, 4) + overlay.addStretch(1) + overlay.addWidget(size_grip, 0, Qt.AlignBottom | Qt.AlignRight) + main_layout.addLayout(overlay) + + def _setup_ui(self): + """Setup the user interface""" + self.setupWrapper() + main_layout = QVBoxLayout(self.content_widget) + main_layout.setContentsMargins(20, 20, 20, 20) + main_layout.setSpacing(15) + + # Add sections + self._add_coordinates_section(main_layout) + self._add_separator(main_layout) + self._add_location_name_section(main_layout) + self._add_separator(main_layout) + self._add_map_section(main_layout) + self._add_separator(main_layout) + self._add_irc_values_section(main_layout) + self._add_separator(main_layout) + self._add_custom_params_section(main_layout) + + main_layout.addStretch() + + self._add_buttons(main_layout) + + def _add_coordinates_section(self, layout): + """Add the coordinates input section""" + coords_row = QHBoxLayout() + coords_row.setSpacing(15) + + self.coords_checkbox = QCheckBox("Enter Coordinates") + coords_row.addWidget(self.coords_checkbox) + coords_row.addStretch() + + lat_label = QLabel("Latitude (°)") + lat_label.setStyleSheet("font-size: 11px;") + coords_row.addWidget(lat_label) + + self.latitude_input = QLineEdit() + self.latitude_input.setMaximumWidth(120) + self.latitude_input.setEnabled(False) + apply_field_style(self.latitude_input) + coords_row.addWidget(self.latitude_input) + + lng_label = QLabel("Longitude (°)") + lng_label.setStyleSheet("font-size: 11px;") + coords_row.addWidget(lng_label) + + self.longitude_input = QLineEdit() + self.longitude_input.setMaximumWidth(120) + self.longitude_input.setEnabled(False) + apply_field_style(self.longitude_input) + coords_row.addWidget(self.longitude_input) + + layout.addLayout(coords_row) + + def _add_location_name_section(self, layout): + """Add the location name input section""" + location_row = QHBoxLayout() + location_row.setSpacing(15) + + self.location_checkbox = QCheckBox("Enter Location Name") + location_row.addWidget(self.location_checkbox) + location_row.addStretch() + + state_label = QLabel("State") + state_label.setStyleSheet("font-size: 11px;") + location_row.addWidget(state_label) + + self.state_combo = NoScrollComboBox() + self.state_combo.setMaximumWidth(150) + self.state_combo.setEnabled(False) + self.state_combo.addItems(list(self.STATE_DISTRICTS.keys())) + apply_field_style(self.state_combo) + location_row.addWidget(self.state_combo) + + district_label = QLabel("District") + district_label.setStyleSheet("font-size: 11px;") + location_row.addWidget(district_label) + + self.district_combo = NoScrollComboBox() + self.district_combo.setMaximumWidth(150) + self.district_combo.setEnabled(False) + self.district_combo.addItems(["Select District"]) + apply_field_style(self.district_combo) + location_row.addWidget(self.district_combo) + + layout.addLayout(location_row) + + def _add_map_section(self, layout): + """Add the map selection section""" + map_section = QVBoxLayout() + map_section.setSpacing(8) + + self.map_checkbox = QCheckBox("Select on Map") + map_section.addWidget(self.map_checkbox) + + # Map placeholder + self.map_placeholder = QLabel() + self.map_placeholder.setAlignment(Qt.AlignCenter) + self.map_placeholder.setMinimumHeight(200) + self.map_placeholder.setText("Map Placeholder\n(Will be added later)") + self.map_placeholder.setEnabled(False) + map_section.addWidget(self.map_placeholder) + + layout.addLayout(map_section) + + def _add_irc_values_section(self, layout): + """Add the IRC 6 (2017) values section""" + results_section = QVBoxLayout() + results_section.setSpacing(8) + + results_title = QLabel("IRC 6 (2017) Values") + results_section.addWidget(results_title) + + self.wind_speed_label = QLabel("Basic Wind Speed (m/sec)") + results_section.addWidget(self.wind_speed_label) + + self.seismic_zone_label = QLabel("Seismic Zone and Zone Factor") + results_section.addWidget(self.seismic_zone_label) + + self.temp_label = QLabel("Shade Air Temperature (°C)") + results_section.addWidget(self.temp_label) + + layout.addLayout(results_section) + + def _add_custom_params_section(self, layout): + """Add the custom loading parameters checkbox""" + self.custom_params_checkbox = QCheckBox("Tabulate Custom Loading Parameters") + layout.addWidget(self.custom_params_checkbox) + + def _add_separator(self, layout): + """Add a horizontal separator line""" + line = QFrame() + line.setFrameShape(QFrame.HLine) + line.setFrameShadow(QFrame.Sunken) + line.setStyleSheet("background-color: #d0d0d0;") + layout.addWidget(line) + + def _add_buttons(self, layout): + """Add OK and Cancel buttons""" + btn_layout = QHBoxLayout() + btn_layout.addStretch() + + ok_btn = QPushButton("OK") + ok_btn.setCursor(Qt.CursorShape.PointingHandCursor) + ok_btn.setMinimumWidth(100) + ok_btn.clicked.connect(self.accept) + btn_layout.addWidget(ok_btn) + + cancel_btn = QPushButton("Cancel") + cancel_btn.setCursor(Qt.CursorShape.PointingHandCursor) + cancel_btn.setMinimumWidth(100) + cancel_btn.clicked.connect(self.reject) + btn_layout.addWidget(cancel_btn) + + layout.addLayout(btn_layout) + + def _connect_signals(self): + """Connect all signal handlers""" + # Enable/disable coordinates inputs + self.coords_checkbox.stateChanged.connect( + lambda state: self._toggle_coordinates_inputs(state == 2) + ) + + # Enable/disable location inputs + self.location_checkbox.stateChanged.connect( + lambda state: self._toggle_location_inputs(state == 2) + ) + + # Handle map checkbox + self.map_checkbox.stateChanged.connect(self._on_map_checkbox_changed) + + # Update districts when state changes + self.state_combo.currentTextChanged.connect(self._on_state_changed) + + def _toggle_coordinates_inputs(self, enabled): + """Enable or disable coordinate input fields""" + self.latitude_input.setEnabled(enabled) + self.longitude_input.setEnabled(enabled) + + def _toggle_location_inputs(self, enabled): + """Enable or disable location input fields""" + self.state_combo.setEnabled(enabled) + self.district_combo.setEnabled(enabled) + + def _on_state_changed(self, state_name): + """Update districts based on selected state""" + districts = self.STATE_DISTRICTS.get(state_name, ["Select District"]) + self.district_combo.clear() + self.district_combo.addItems(districts) + + def _on_map_checkbox_changed(self, state): + """Handle map checkbox state changes""" + enabled = (state == 2) + self.map_placeholder.setEnabled(enabled) + + if enabled: + self.map_placeholder.setStyleSheet(""" + QLabel { + border: 2px solid #90AF13; + background-color: white; + padding: 20px; + color: #666666; + } + """) + self.map_placeholder.setText( + "Map Placeholder\n(Click to select location)\n(Will be implemented later)" + ) + else: + self.map_placeholder.setStyleSheet(""" + QLabel { + border: 1px solid #e0e0e0; + background-color: #f5f5f5; + padding: 20px; + color: #999999; + } + """) + self.map_placeholder.setText("Map Placeholder\n(Will be added later)") + + def get_selected_location(self): + """ + Get the selected location data + + Returns: + dict: Dictionary containing location information based on selection method + """ + result = { + 'method': None, + 'data': {} + } + + if self.coords_checkbox.isChecked(): + result['method'] = 'coordinates' + result['data'] = { + 'latitude': self.latitude_input.text(), + 'longitude': self.longitude_input.text() + } + elif self.location_checkbox.isChecked(): + result['method'] = 'location_name' + result['data'] = { + 'state': self.state_combo.currentText(), + 'district': self.district_combo.currentText() + } + elif self.map_checkbox.isChecked(): + result['method'] = 'map' + result['data'] = {} # Will be populated when map is implemented + + result['custom_params'] = self.custom_params_checkbox.isChecked() + + return result \ No newline at end of file diff --git a/src/osdagbridge/desktop/ui/docks/input_dock.py b/src/osdagbridge/desktop/ui/docks/input_dock.py index b423b174..73e8eab3 100644 --- a/src/osdagbridge/desktop/ui/docks/input_dock.py +++ b/src/osdagbridge/desktop/ui/docks/input_dock.py @@ -6,19 +6,21 @@ QComboBox, QScrollArea, QLabel, QFormLayout, QLineEdit, QGroupBox, QSizePolicy, QMessageBox, QInputDialog, QDialog, QCheckBox, QFrame, QDialogButtonBox, QStackedWidget ) -from PySide6.QtCore import Qt, QRegularExpression, QSize +from PySide6.QtCore import Qt, QRegularExpression, QSize, QTimer, QPoint, QEvent from PySide6.QtGui import QPixmap, QDoubleValidator, QRegularExpressionValidator, QIcon from PySide6.QtSvgWidgets import * from osdagbridge.core.utils.common import * -from osdagbridge.desktop.ui.additional_inputs import AdditionalInputs +from osdagbridge.desktop.ui.dialogs.additional_inputs import AdditionalInputs from osdagbridge.desktop.ui.utils.custom_buttons import DockCustomButton +from osdagbridge.desktop.ui.dialogs.project_location import ProjectLocationDialog + STEEL_MEMBER_FIELDS = [ "Ultimate Tensile Strength, Fu (MPa)", "Yield Strength, Fy (MPa)", "Modulus of Elasticity, E (GPa)", "Modulus of Rigidity, G (GPa)", - "Poisson’s Ratio, ν", + "Poisson's Ratio, ν", "Thermal Expansion Coefficient, (×10⁻⁶/°C)", ] @@ -119,7 +121,11 @@ def apply_field_style(widget): background-color: #90AF13; color: black; border: 1px solid #94b816; - } + } + QComboBox:disabled{ + background: #f1f1f1; + color: #666; + } """ widget.setStyleSheet(style) elif isinstance(widget, QLineEdit): @@ -132,532 +138,16 @@ def apply_field_style(widget): color: #000000; font-weight: normal; } + QLineEdit:disabled{ + background: #f1f1f1; + color: #666; + } """) class MaterialPropertiesDialog(QDialog): - MEMBER_OPTIONS = ["Girder", "Cross Bracing", "End Diaphragm", "Deck"] - STEEL_MEMBERS = {"Girder", "Cross Bracing", "End Diaphragm"} - - def __init__(self, parent=None): - super().__init__(parent) - self.setWindowTitle("Material Properties") - self.setMinimumWidth(580) - self.setStyleSheet("background-color: white;") - - self.parent_dock = parent - self._loading = False - self.current_member = None - self.member_data = {} - - self.member_combo = NoScrollComboBox() - self.member_combo.addItems(self.MEMBER_OPTIONS) - apply_field_style(self.member_combo) - - self.material_combo = NoScrollComboBox() - apply_field_style(self.material_combo) - - main_layout = QVBoxLayout(self) - main_layout.setContentsMargins(20, 16, 20, 16) - - # Create a container widget for all form fields - form_container = QWidget() - form_layout = QVBoxLayout(form_container) - form_layout.setContentsMargins(0, 0, 0, 0) - form_layout.setSpacing(10) - - # Member row - member_row = QHBoxLayout() - member_row.setContentsMargins(0, 0, 0, 0) - member_row.setSpacing(18) - member_label = QLabel("Member*:") - member_label.setStyleSheet("font-size: 12px; color: #2d2d2d;") - member_label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) - member_label.setFixedWidth(280) - self.member_combo.setFixedWidth(242) - member_row.addWidget(member_label) - member_row.addWidget(self.member_combo) - member_row.addStretch() - form_layout.addLayout(member_row) - - # Material row - material_row = QHBoxLayout() - material_row.setContentsMargins(0, 0, 0, 0) - material_row.setSpacing(18) - material_label = QLabel("Material*:") - material_label.setStyleSheet("font-size: 12px; color: #2d2d2d;") - material_label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) - material_label.setFixedWidth(280) - self.material_combo.setFixedWidth(242) - material_row.addWidget(material_label) - material_row.addWidget(self.material_combo) - material_row.addStretch() - form_layout.addLayout(material_row) - - main_layout.addWidget(form_container) - - self.stack = QStackedWidget() - self.stack.setContentsMargins(0, 0, 0, 0) - self.steel_page = self._build_steel_form() - self.deck_page = self._build_deck_form() - self.stack.addWidget(self.steel_page) - self.stack.addWidget(self.deck_page) - main_layout.addWidget(self.stack) - - # Updated default row with proper alignment - default_row = QHBoxLayout() - default_row.setContentsMargins(0, 0, 0, 0) - default_row.setSpacing(18) - default_label = QLabel("Default") - default_label.setStyleSheet("font-size: 12px; color: #2d2d2d;") - default_label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) - default_label.setFixedWidth(280) - self.default_checkbox = QCheckBox() - # Create container for checkbox to align it to the left - checkbox_container = QWidget() - checkbox_layout = QHBoxLayout(checkbox_container) - checkbox_layout.setContentsMargins(0, 0, 0, 0) - checkbox_layout.setSpacing(0) - checkbox_layout.addWidget(self.default_checkbox) - checkbox_layout.addStretch() - - default_row.addWidget(default_label) - default_row.addWidget(checkbox_container) - main_layout.addLayout(default_row) - - self.member_combo.currentTextChanged.connect(self._on_member_changed) - self.material_combo.currentTextChanged.connect(self._on_material_changed) - self.default_checkbox.stateChanged.connect(self._on_default_toggled) - - self._initialize_member_data() - self._on_member_changed(self.member_combo.currentText()) - - def closeEvent(self, event): - self._save_current_member_form() - super().closeEvent(event) - - def _build_steel_form(self): - widget = QWidget() - layout = QVBoxLayout(widget) - layout.setContentsMargins(0, 0, 0, 0) - layout.setSpacing(10) - self.steel_field_inputs = {} - for label_text in STEEL_MEMBER_FIELDS: - row = QHBoxLayout() - row.setContentsMargins(0, 0, 0, 0) - row.setSpacing(18) - label = QLabel(label_text) - label.setStyleSheet("font-size: 12px; color: #2d2d2d;") - label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) - label.setFixedWidth(280) - line_edit = QLineEdit() - line_edit.setFixedWidth(242) - apply_field_style(line_edit) - # Add validator for 1 decimal place - line_edit.setValidator(QDoubleValidator(0.0, 99999.0, 1)) - line_edit.textEdited.connect(self._handle_user_override) - self.steel_field_inputs[label_text] = line_edit - row.addWidget(label) - row.addWidget(line_edit) - row.addStretch() - layout.addLayout(row) - layout.addStretch() - return widget - - def _build_deck_form(self): - widget = QWidget() - layout = QVBoxLayout(widget) - layout.setSpacing(10) - self.deck_field_inputs = {} - for label_text in DECK_MEMBER_FIELDS: - row = QHBoxLayout() - row.setSpacing(18) - label = QLabel(label_text) - label.setStyleSheet("font-size: 12px; color: #2d2d2d;") - label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) - label.setFixedWidth(280) - if label_text == "Ecm Multiplication Factor": - self.deck_factor_combo = NoScrollComboBox() - self.deck_factor_combo.addItems(ECM_FACTOR_LABELS) - self.deck_factor_combo.setFixedWidth(242) - apply_field_style(self.deck_factor_combo) - self.deck_factor_combo.currentTextChanged.connect(self._on_factor_changed) - - self.deck_factor_custom_input = QLineEdit() - apply_field_style(self.deck_factor_custom_input) - self.deck_factor_custom_input.setPlaceholderText("Custom factor") - self.deck_factor_custom_input.setFixedWidth(242) - self.deck_factor_custom_input.setVisible(False) - self.deck_factor_custom_input.setEnabled(False) - self.deck_factor_custom_input.setValidator(QDoubleValidator(0.1, 5.0, 1)) - self.deck_factor_custom_input.textEdited.connect(self._handle_user_override) - - row.addWidget(label) - row.addWidget(self.deck_factor_combo) - row.addStretch() - - # Add custom input row (hidden by default) - custom_row = QHBoxLayout() - custom_row.setContentsMargins(0, 0, 0, 0) - custom_row.setSpacing(18) - custom_label = QLabel("") # Empty label for alignment - custom_label.setFixedWidth(280) - custom_row.addWidget(custom_label) - custom_row.addWidget(self.deck_factor_custom_input) - custom_row.addStretch() - layout.addLayout(custom_row) - - self.deck_field_inputs[label_text] = self.deck_factor_combo - else: - line_edit = QLineEdit() - line_edit.setFixedWidth(242) - apply_field_style(line_edit) - # Add validator for 1 decimal place - line_edit.setValidator(QDoubleValidator(0.0, 99999.0, 1)) - line_edit.textEdited.connect(self._handle_user_override) - row.addWidget(label) - row.addWidget(line_edit) - row.addStretch() - self.deck_field_inputs[label_text] = line_edit - layout.addLayout(row) - layout.addStretch() - return widget - - def _initialize_member_data(self): - for member in self.MEMBER_OPTIONS: - material = self._get_parent_grade(member) - fields = self._default_fields_for_member(member, material) - self.member_data[member] = { - "material": material, - "fields": fields, - "is_default": True, - "factor_label": DEFAULT_ECM_FACTOR_LABEL if member == "Deck" else None, - "custom_factor": "1.0" if member == "Deck" else None, - } - - def _default_fields_for_member(self, member, material=None, factor_label=None, custom_factor=None): - if member == "Deck": - grade = material or self._get_parent_grade(member) or (VALUES_DECK_CONCRETE_GRADE[0] if VALUES_DECK_CONCRETE_GRADE else "") - factor_label = factor_label or DEFAULT_ECM_FACTOR_LABEL - factor_value = self._factor_value_from_label(factor_label, custom_factor) - return self._deck_defaults(grade, factor_value) - grade = material or self._get_parent_grade(member) or (VALUES_MATERIAL[0] if VALUES_MATERIAL else "") - return self._steel_defaults(grade) - - def _steel_defaults(self, grade): - grade_value = self._extract_numeric_grade(grade) - defaults = STEEL_GRADE_BASE_VALUES.get(grade_value, STEEL_GRADE_BASE_VALUES[250]) - return { - "Ultimate Tensile Strength, Fu (MPa)": "{:.1f}".format(defaults["Fu"]), - "Yield Strength, Fy (MPa)": "{:.1f}".format(defaults["Fy"]), - "Modulus of Elasticity, E (GPa)": "{:.1f}".format(STEEL_MODULUS_E_GPA), - "Modulus of Rigidity, G (GPa)": "{:.1f}".format(STEEL_MODULUS_G_GPA), - "Poisson's Ratio, ν": "{:.1f}".format(STEEL_POISSON_RATIO), - "Thermal Expansion Coefficient, (×10⁻⁶/°C)": "{:.1f}".format(STEEL_THERMAL_COEFF), - } - - def _deck_defaults(self, grade, factor_value): - strength = self._extract_numeric_grade(grade, default=25) - fck = float(strength) - fctm = round(0.7 * math.sqrt(fck), 1) - ecm = round(5.0 * math.sqrt(fck) * factor_value, 1) - return { - "Characteristic Compressive (Cube) Strength of Concrete, (fck)cu (MPa)": "{:.1f}".format(fck), - "Mean Tensile Strength of Concrete, fctm (MPa)": "{:.1f}".format(fctm), - "Secant Modulus of Elasticity of Concrete, Ecm (GPa)": "{:.1f}".format(ecm), - "Ecm Multiplication Factor": "{:.1f}".format(factor_value), - } - - def _extract_numeric_grade(self, grade, default=250): - digits = ''.join(ch for ch in grade if ch.isdigit()) - try: - return int(digits) if digits else default - except ValueError: - return default - - def _materials_for_member(self, member): - return VALUES_DECK_CONCRETE_GRADE if member == "Deck" else VALUES_MATERIAL - - def _on_member_changed(self, member): - if self.current_member: - self._save_current_member_form() - - self.current_member = member - is_deck = member == "Deck" - self.stack.setCurrentWidget(self.deck_page if is_deck else self.steel_page) - - data = self.member_data.get(member) - if not data: - self.member_data[member] = self._create_default_entry(member) - data = self.member_data[member] - - if data.get("is_default"): - self._apply_defaults_for_member(member, update_ui=False) - - materials = self._materials_for_member(member) - self._loading = True - self.material_combo.clear() - self.material_combo.addItems(materials) - if data["material"] in materials: - self.material_combo.setCurrentText(data["material"]) - elif materials: - self.material_combo.setCurrentIndex(0) - data["material"] = self.material_combo.currentText() - - self.default_checkbox.setChecked(data.get("is_default", False)) - if is_deck: - self._populate_deck_fields(data) - else: - self._populate_steel_fields(data) - self._loading = False - - def _populate_steel_fields(self, data): - for label, widget in self.steel_field_inputs.items(): - value = data["fields"].get(label, "") - # Format to 1 decimal place - try: - formatted_value = "{:.1f}".format(float(value)) - widget.setText(formatted_value) - except (ValueError, TypeError): - widget.setText(value) - - def _populate_deck_fields(self, data): - for label, widget in self.deck_field_inputs.items(): - if label == "Ecm Multiplication Factor": - factor_label = data.get("factor_label", DEFAULT_ECM_FACTOR_LABEL) - if factor_label not in ECM_FACTOR_LABELS: - factor_label = DEFAULT_ECM_FACTOR_LABEL - self.deck_factor_combo.blockSignals(True) - self.deck_factor_combo.setCurrentText(factor_label) - self.deck_factor_combo.blockSignals(False) - self._update_custom_factor_visibility(factor_label) - self.deck_factor_custom_input.blockSignals(True) - custom_val = data.get("custom_factor", "1.0") - try: - formatted_custom = "{:.1f}".format(float(custom_val)) - self.deck_factor_custom_input.setText(formatted_custom) - except (ValueError, TypeError): - self.deck_factor_custom_input.setText(custom_val) - self.deck_factor_custom_input.blockSignals(False) - else: - value = data["fields"].get(label, "") - # Format to 1 decimal place - try: - formatted_value = "{:.1f}".format(float(value)) - widget.setText(formatted_value) - except (ValueError, TypeError): - widget.setText(value) - - def _save_current_member_form(self): - if not self.current_member: - return - data = self.member_data.setdefault(self.current_member, self._create_default_entry(self.current_member)) - data["material"] = self.material_combo.currentText() - if self.current_member == "Deck": - for label, widget in self.deck_field_inputs.items(): - if label == "Ecm Multiplication Factor": - data["factor_label"] = self.deck_factor_combo.currentText() - data["custom_factor"] = self.deck_factor_custom_input.text() or "1.0" - else: - data["fields"][label] = widget.text() - factor_value = self._factor_value_from_label(data["factor_label"], data.get("custom_factor")) - data["fields"]["Ecm Multiplication Factor"] = "{:.1f}".format(factor_value) - else: - for label, widget in self.steel_field_inputs.items(): - data["fields"][label] = widget.text() - data["is_default"] = self.default_checkbox.isChecked() - - def _create_default_entry(self, member): - material = self._get_parent_grade(member) - return { - "material": material, - "fields": self._default_fields_for_member(member, material), - "is_default": True, - "factor_label": DEFAULT_ECM_FACTOR_LABEL if member == "Deck" else None, - "custom_factor": "1.0" if member == "Deck" else None, - } - - def _apply_defaults_for_member(self, member, update_ui=True): - data = self.member_data.setdefault(member, self._create_default_entry(member)) - grade = self._get_parent_grade(member) or data.get("material") - materials = self._materials_for_member(member) - if grade not in materials and materials: - grade = materials[0] - data["material"] = grade - if member == "Deck": - data["factor_label"] = DEFAULT_ECM_FACTOR_LABEL - data["custom_factor"] = "1.0" - factor_value = self._factor_value_from_label(DEFAULT_ECM_FACTOR_LABEL) - data["fields"] = self._deck_defaults(grade, factor_value) - else: - data["fields"] = self._steel_defaults(grade) - data["is_default"] = True - - if update_ui and member == self.current_member: - self._loading = True - self.material_combo.setCurrentText(grade) - if member == "Deck": - self._populate_deck_fields(data) - else: - self._populate_steel_fields(data) - self.default_checkbox.setChecked(True) - self._loading = False - - def _factor_value_from_label(self, label, custom_factor=None): - for text, value in ECM_FACTOR_OPTIONS: - if text == label: - if value is None: - try: - return float(custom_factor) if custom_factor else 1.0 - except ValueError: - return 1.0 - return value - return 1.0 - - def _reset_current_member_to_defaults(self): - if not self.current_member: - return - - self._apply_defaults_for_member(self.current_member, update_ui=False) - data = self.member_data.get(self.current_member) - if not data: - return - - target_material = data.get("material", "") - self._loading = True - if target_material: - index = self.material_combo.findText(target_material) - if index >= 0: - self.material_combo.setCurrentIndex(index) - elif self.material_combo.count() > 0: - self.material_combo.setCurrentIndex(0) - data["material"] = self.material_combo.currentText() - if self.current_member == "Deck": - self._populate_deck_fields(data) - else: - self._populate_steel_fields(data) - self._loading = False - - self.default_checkbox.blockSignals(True) - self.default_checkbox.setChecked(True) - self.default_checkbox.blockSignals(False) - self._save_current_member_form() - - def _update_custom_factor_visibility(self, label): - is_custom = label == CUSTOM_ECM_FACTOR_LABEL - self.deck_factor_custom_input.setVisible(is_custom) - self.deck_factor_custom_input.setEnabled(is_custom) - self.deck_factor_combo.setVisible(not is_custom) - - def _on_material_changed(self, material): - if self._loading: - return - data = self.member_data.get(self.current_member) - if data: - data["material"] = material - self._handle_user_override() - - def _on_default_toggled(self, state): - if self._loading: - return - try: - check_state = Qt.CheckState(state) - except ValueError: - check_state = Qt.CheckState.Checked if bool(state) else Qt.CheckState.Unchecked - if check_state == Qt.CheckState.Checked: - self._reset_current_member_to_defaults() - else: - data = self.member_data.get(self.current_member) - if data: - data["is_default"] = False - - def _on_factor_changed(self, label): - self._update_custom_factor_visibility(label) - self._handle_user_override() - - def _handle_user_override(self): - if self._loading: - return - if self.default_checkbox.isChecked(): - self._loading = True - self.default_checkbox.setChecked(False) - self._loading = False - data = self.member_data.get(self.current_member) - if data: - data["is_default"] = False - self._save_current_member_form() - - def _get_parent_grade(self, member): - parent = self.parent_dock - if not parent: - return "" - mapping = { - "Girder": getattr(parent, "girder_combo", None), - "Cross Bracing": getattr(parent, "cross_bracing_combo", None), - "End Diaphragm": getattr(parent, "end_diaphragm_combo", None), - "Deck": getattr(parent, "deck_combo", None), - } - combo = mapping.get(member) - return combo.currentText() if combo else "" - - def set_member(self, member): - index = self.member_combo.findText(member) - if index >= 0: - self.member_combo.setCurrentIndex(index) - - def sync_with_parent_defaults(self): - for member, data in self.member_data.items(): - if data.get("is_default"): - self._apply_defaults_for_member(member, update_ui=(member == self.current_member)) -def create_group_box(title): - """Create a styled group box""" - group_box = QGroupBox(title) - group_box.setStyleSheet(""" - QGroupBox { - font-weight: bold; - font-size: 12px; - color: #333; - border: 1px solid #90AF13; - border-radius: 4px; - margin-top: 0.8em; - padding: 10px; - } - QGroupBox::title { - subcontrol-origin: margin; - subcontrol-position: top left; - left: 8px; - padding: 0 4px; - margin-top: 4px; - background-color: white; - color: #333; - } - """) - return group_box - - -def create_form_row(label_text, widget, tooltip=None): - """Create a horizontal layout with label and widget side by side""" - row = QHBoxLayout() - row.setSpacing(10) - - label = QLabel(label_text) - label.setStyleSheet(""" - QLabel { - color: #000000; - font-size: 12px; - background: transparent; - } - """) - label.setMinimumWidth(140) - label.setMaximumWidth(140) - - if tooltip: - widget.setToolTip(tooltip) - - row.addWidget(label) - row.addWidget(widget, 1) - - return row + # ... (Keep all MaterialPropertiesDialog code unchanged) + pass class InputDock(QWidget): @@ -675,7 +165,7 @@ def __init__(self, backend, parent): self.additional_inputs_widget = None self.material_dialog = None self.additional_inputs_btn = None - self.lock_button = None + self.lock_btn = None self.scroll_area = None self.is_locked = False @@ -739,306 +229,92 @@ def on_structure_type_changed(self, text): else: if hasattr(self, 'structure_note'): self.structure_note.setVisible(False) - - def on_project_location_changed(self, text): - """Handle project location combo box changes""" - if text == "Custom": - custom_location, ok = QInputDialog.getText( - self, - "Custom Location", - "Enter city name for load calculations", - QLineEdit.Normal, - "" - ) - if ok and custom_location.strip(): - self.custom_location_input = custom_location.strip() - QMessageBox.information( - self, - "Custom Location Set", - f"Custom location '{custom_location.strip()}' has been set.\n\n" - f"Note: Please ensure load calculation data is available for this location.", - QMessageBox.Ok - ) - self.project_location_combo.addItem(custom_location.strip()) - elif ok: - QMessageBox.warning( - self, - "No Location Entered", - "Please enter a valid city name or select from the dropdown.", - QMessageBox.Ok - ) - if self.project_location_combo: - self.project_location_combo.setCurrentIndex(0) - + def show_project_location_dialog(self): """Show Project Location selection dialog""" - state_districts = { - "Select State": ["Select District"], - "Delhi": ["Central Delhi", "East Delhi", "New Delhi", "North Delhi", "North East Delhi", - "North West Delhi", "South Delhi", "South East Delhi", "South West Delhi", "West Delhi"], - "Maharashtra": ["Mumbai", "Pune", "Nagpur", "Thane", "Nashik", "Aurangabad", "Solapur", - "Amravati", "Kolhapur", "Raigad", "Satara", "Sangli"], - "Karnataka": ["Bangalore", "Mysore", "Hubli", "Belgaum", "Mangalore", "Gulbarga", - "Bellary", "Bijapur", "Shimoga", "Tumkur", "Davangere"], - "Tamil Nadu": ["Chennai", "Coimbatore", "Madurai", "Tiruchirappalli", "Salem", "Tirunelveli", - "Tiruppur", "Erode", "Vellore", "Thoothukudi", "Dindigul"], - "West Bengal": ["Kolkata", "Howrah", "Darjeeling", "Siliguri", "Asansol", "Durgapur", - "Bardhaman", "Malda", "Jalpaiguri", "Murshidabad", "Nadia"] - } - - dialog = QDialog(self) - dialog.setWindowTitle("Project Location") - dialog.setMinimumWidth(850) - dialog.setMinimumHeight(650) - - # Set white background for the entire dialog - dialog.setStyleSheet(""" - QDialog { - background-color: white; - } - QCheckBox { - color: black; - } - QLabel { - color: black; - } - """) - - main_layout = QVBoxLayout(dialog) - main_layout.setContentsMargins(20, 20, 20, 20) - main_layout.setSpacing(15) - - # === Enter Coordinates Row === - coords_row = QHBoxLayout() - coords_row.setSpacing(15) - - self.coords_checkbox = QCheckBox("Enter Coordinates") - coords_row.addWidget(self.coords_checkbox) - - coords_row.addStretch() - - lat_label = QLabel("Latitude (°)") - lat_label.setStyleSheet("font-size: 11px;") - coords_row.addWidget(lat_label) - - self.latitude_input = QLineEdit() - self.latitude_input.setMaximumWidth(120) - self.latitude_input.setEnabled(False) - apply_field_style(self.latitude_input) - coords_row.addWidget(self.latitude_input) - - lng_label = QLabel("Longitude (°)") - lng_label.setStyleSheet("font-size: 11px;") - coords_row.addWidget(lng_label) - - self.longitude_input = QLineEdit() - self.longitude_input.setMaximumWidth(120) - self.longitude_input.setEnabled(False) - apply_field_style(self.longitude_input) - coords_row.addWidget(self.longitude_input) - - main_layout.addLayout(coords_row) - - # Separator line - line1 = QFrame() - line1.setFrameShape(QFrame.HLine) - line1.setFrameShadow(QFrame.Sunken) - line1.setStyleSheet("background-color: #d0d0d0;") - main_layout.addWidget(line1) - - # === Enter Location Name Row === - location_row = QHBoxLayout() - location_row.setSpacing(15) - - self.location_checkbox = QCheckBox("Enter Location Name") - location_row.addWidget(self.location_checkbox) - - location_row.addStretch() - - state_label = QLabel("State") - state_label.setStyleSheet("font-size: 11px;") - location_row.addWidget(state_label) - - self.state_combo = NoScrollComboBox() - self.state_combo.setMaximumWidth(150) - self.state_combo.setEnabled(False) - self.state_combo.addItems(list(state_districts.keys())) - apply_field_style(self.state_combo) - location_row.addWidget(self.state_combo) - - district_label = QLabel("District") - district_label.setStyleSheet("font-size: 11px;") - location_row.addWidget(district_label) - - self.district_combo = NoScrollComboBox() - self.district_combo.setMaximumWidth(150) - self.district_combo.setEnabled(False) - self.district_combo.addItems(["Select District"]) - apply_field_style(self.district_combo) - location_row.addWidget(self.district_combo) - - main_layout.addLayout(location_row) - - # Separator line - line2 = QFrame() - line2.setFrameShape(QFrame.HLine) - line2.setFrameShadow(QFrame.Sunken) - line2.setStyleSheet("background-color: #d0d0d0;") - main_layout.addWidget(line2) - - # === Select on Map Section === - map_section = QVBoxLayout() - map_section.setSpacing(8) - - self.map_checkbox = QCheckBox("Select on Map") - map_section.addWidget(self.map_checkbox) - - # Map placeholder - self.map_placeholder = QLabel() - self.map_placeholder.setStyleSheet(""" - QLabel { - border: 1px solid #e0e0e0; - background-color: white; - padding: 20px; - color: #999999; - } - """) - self.map_placeholder.setAlignment(Qt.AlignCenter) - self.map_placeholder.setMinimumHeight(200) - self.map_placeholder.setText("Map Placeholder\n(Will be added later)") - self.map_placeholder.setEnabled(False) # Disabled by default - map_section.addWidget(self.map_placeholder) - - main_layout.addLayout(map_section) - - # Separator line - line3 = QFrame() - line3.setFrameShape(QFrame.HLine) - line3.setFrameShadow(QFrame.Sunken) - line3.setStyleSheet("background-color: #d0d0d0;") - main_layout.addWidget(line3) - - # === IRC 6 (2017) Values Section === - results_section = QVBoxLayout() - results_section.setSpacing(8) - - results_title = QLabel("IRC 6 (2017) Values") - results_title.setStyleSheet("font-size: 12px; font-weight: bold; color: #4CAF50;") - results_section.addWidget(results_title) - - self.wind_speed_label = QLabel("Basic Wind Speed (m/sec)") - self.wind_speed_label.setStyleSheet("font-size: 11px; color: #4CAF50;") - results_section.addWidget(self.wind_speed_label) - - self.seismic_zone_label = QLabel("Seismic Zone and Zone Factor") - self.seismic_zone_label.setStyleSheet("font-size: 11px; color: #4CAF50;") - results_section.addWidget(self.seismic_zone_label) - - self.temp_label = QLabel("Shade Air Temperature (°C)") - self.temp_label.setStyleSheet("font-size: 11px; color: #4CAF50;") - results_section.addWidget(self.temp_label) - - main_layout.addLayout(results_section) - - # Separator line - line4 = QFrame() - line4.setFrameShape(QFrame.HLine) - line4.setFrameShadow(QFrame.Sunken) - line4.setStyleSheet("background-color: #d0d0d0;") - main_layout.addWidget(line4) - - # === Custom Loading Parameters Checkbox === - self.custom_params_checkbox = QCheckBox("Tabulate Custom Loading Parameters") - main_layout.addWidget(self.custom_params_checkbox) - - main_layout.addStretch() - - # === Bottom Buttons === - btn_layout = QHBoxLayout() - btn_layout.addStretch() - - ok_btn = QPushButton("OK") - ok_btn.setCursor(Qt.CursorShape.PointingHandCursor) - ok_btn.setStyleSheet(""" - QPushButton { - background-color: white; - color: #333; - border: 1px solid #c0c0c0; - border-radius: 3px; - padding: 6px 16px; - min-height: 28px; - } - QPushButton:hover { - background-color: #f5f5f5; - } - """) - ok_btn.setMinimumWidth(100) - ok_btn.clicked.connect(dialog.accept) - btn_layout.addWidget(ok_btn) - - cancel_btn = QPushButton("Cancel") - cancel_btn.setCursor(Qt.CursorShape.PointingHandCursor) - cancel_btn.setStyleSheet(""" - QPushButton { - background-color: white; - color: #333; - border: 1px solid #c0c0c0; - border-radius: 3px; - padding: 6px 16px; - min-height: 28px; - } - QPushButton:hover { - background-color: #f5f5f5; - } - """) - cancel_btn.setMinimumWidth(100) - cancel_btn.clicked.connect(dialog.reject) - btn_layout.addWidget(cancel_btn) - - main_layout.addLayout(btn_layout) - - # Function to update districts based on selected state - def on_state_changed(state_name): - districts = state_districts.get(state_name, ["Select District"]) - self.district_combo.clear() - self.district_combo.addItems(districts) - - # Function to handle map checkbox - def on_map_checkbox_changed(state): - enabled = (state == 2) - self.map_placeholder.setEnabled(enabled) - if enabled: - self.map_placeholder.setStyleSheet(""" - QLabel { - border: 2px solid #90AF13; - background-color: white; - padding: 20px; - color: #666666; - } - """) - self.map_placeholder.setText("Map Placeholder\n(Click to select location)\n(Will be implemented later)") - else: - self.map_placeholder.setStyleSheet(""" - QLabel { - border: 1px solid #e0e0e0; - background-color: #f5f5f5; - padding: 20px; - color: #999999; - } - """) - self.map_placeholder.setText("Map Placeholder\n(Will be added later)") - - # Connect checkbox signals to enable/disable fields - self.coords_checkbox.stateChanged.connect(lambda state: self.latitude_input.setEnabled(state == 2) or self.longitude_input.setEnabled(state == 2)) - self.location_checkbox.stateChanged.connect(lambda state: self.state_combo.setEnabled(state == 2) or self.district_combo.setEnabled(state == 2)) - self.map_checkbox.stateChanged.connect(on_map_checkbox_changed) - - # Connect state combo to update districts - self.state_combo.currentTextChanged.connect(on_state_changed) + dialog = ProjectLocationDialog() if dialog.exec() == QDialog.Accepted: - pass + location_data = dialog.get_selected_location() + + # Process the location data as needed + if location_data['method'] == 'coordinates': + lat = location_data['data']['latitude'] + lon = location_data['data']['longitude'] + print(f"Selected coordinates: {lat}, {lon}") + + elif location_data['method'] == 'location_name': + state = location_data['data']['state'] + district = location_data['data']['district'] + print(f"Selected location: {district}, {state}") + + elif location_data['method'] == 'map': + print("Map selection (to be implemented)") + + if location_data['custom_params']: + print("Custom loading parameters requested") + + # Lock-Tooltip-Events-Starts------------------------------------------------------------------------- + def eventFilter(self, obj, event): + # Check if it's the scroll area and it's a mouse press + if obj == self.scroll_area and event.type() == QEvent.MouseButtonPress: + if self.is_locked: + self.show_lock_tooltip() + return True # Block the event + return super().eventFilter(obj, event) + def clear_force_hover(self): + if self.lock_btn: + self.lock_btn.setProperty("forceHover", False) + self.lock_btn.style().polish(self.lock_btn) + self.lock_btn.update() + + def show_lock_tooltip(self): + # Stop any existing timer first + if hasattr(self, 'tooltip_timer') and self.tooltip_timer.isActive(): + self.tooltip_timer.stop() + + # Position tooltip to the right of the lock button + lock_global_pos = self.lock_btn.mapToGlobal(self.lock_btn.rect().topRight()) + tooltip_pos = lock_global_pos + QPoint(5, 0) + self.lock_btn.setProperty("forceHover", True) + self.lock_btn.style().polish(self.lock_btn) + self.lock_btn.update() + + # Adjust size and position + self.lock_btn_tooltip.adjustSize() + self.lock_btn_tooltip.move(tooltip_pos) + self.lock_btn_tooltip.show() + self.lock_btn_tooltip.raise_() + + # Hide after 3 seconds + if not hasattr(self, 'tooltip_timer'): + self.tooltip_timer = QTimer() + self.tooltip_timer.setSingleShot(True) + self.tooltip_timer.timeout.connect(self.lock_btn_tooltip.hide) + self.tooltip_timer.timeout.connect(self.clear_force_hover) + + self.tooltip_timer.start(3000) + + def toggle_lock(self): + self.is_locked = not self.is_locked + self.lock_btn.setChecked(self.is_locked) + self.scroll_area.setDisabled(self.is_locked) + self.update_lock_icon() + + def update_lock_icon(self): + if self.lock_btn: + if self.is_locked: + self.lock_btn.setIcon(QIcon(":/vectors/lock_close.svg")) + else: + self.lock_btn.setIcon(QIcon(":/vectors/lock_open.svg")) + + def paintEvent(self, event): + self.update_lock_icon() + return super().paintEvent(event) + + # Lock-Tooltip-Events-Ends------------------------------------------------------------------------- + def build_left_panel(self, field_list): left_layout = QVBoxLayout(self.left_container) left_layout.setContentsMargins(0, 0, 0, 0) @@ -1071,7 +347,6 @@ def build_left_panel(self, field_list): input_dock_btn.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) top_bar.addWidget(input_dock_btn) - # Additional Inputs button with lock icon on the right self.additional_inputs_btn = QPushButton("Additional Inputs") self.additional_inputs_btn.setCursor(Qt.CursorShape.PointingHandCursor) self.additional_inputs_btn.setStyleSheet(""" @@ -1100,16 +375,57 @@ def build_left_panel(self, field_list): self.additional_inputs_btn.clicked.connect(self.show_additional_inputs) top_bar.addWidget(self.additional_inputs_btn) - # Load the icon from resources - self.lock_button = QPushButton() - self.lock_button.setCursor(Qt.CursorShape.PointingHandCursor) - self.lock_button.setIconSize(QSize(30, 30)) - self.lock_button.setStyleSheet("border: none;") - self.lock_button.clicked.connect(self._toggle_lock_state) - top_bar.addWidget(self.lock_button) - + # Lock button + self.lock_btn = QPushButton() + self.lock_btn.setStyleSheet(""" + QPushButton { + background-color: #f4f4f4; + border: none; + padding: 7px; + border-radius: 4px; + } + QPushButton:hover { + background-color: #e0e0e0; + } + QPushButton:checked { + background-color: #FFA500; + } + QPushButton:unchecked { + background-color: #f4f4f4; + } + QPushButton:unchecked:hover { + background-color: #e0e0e0; + } + QPushButton:checked:hover { + background-color: #fa7a02; + } + """) + self.lock_btn.setCursor(Qt.CursorShape.PointingHandCursor) + self.lock_btn.setObjectName("lock_btn") + self.lock_btn.setCheckable(True) + self.lock_btn.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) + self.lock_btn.clicked.connect(self.toggle_lock) + top_bar.addWidget(self.lock_btn) panel_layout.addLayout(top_bar) + #-Lock-ToolTip-------------------------------------- + self.lock_btn_tooltip = QLabel("Unlock to Edit") + self.lock_btn_tooltip.setStyleSheet(""" + QLabel{ + background-color: #f1f1f1; + color: #000000; + border: 1px solid #90AF13; + padding: 4px; + font-size: 15px; + border-radius: 0px; + qproperty-alignment: AlignVCenter; + } + """) + self.lock_btn_tooltip.setObjectName("lock_btn_tooltip") + self.lock_btn_tooltip.setWindowFlags(Qt.ToolTip) + self.lock_btn_tooltip.hide() + #-------------------------------------------------- + # Scroll area scroll_area = QScrollArea() self.scroll_area = scroll_area @@ -1117,6 +433,7 @@ def build_left_panel(self, field_list): scroll_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) scroll_area.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) scroll_area.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) + scroll_area.installEventFilter(self) scroll_area.setStyleSheet(""" QScrollArea { background: transparent; @@ -1164,6 +481,7 @@ def build_left_panel(self, field_list): group_container_layout.setContentsMargins(0, 0, 0, 0) group_container_layout.setSpacing(12) + # === Type of Structure Box === type_box = QGroupBox("Type of Structure") type_box.setStyleSheet(""" @@ -1269,17 +587,15 @@ def build_left_panel(self, field_list): QPushButton:hover { background-color: #7a9a12; } + QPushButton:disabled{ + background: #D0D0D0; + color: #666; + } + """) add_here_btn.clicked.connect(self.show_project_location_dialog) loc_header.addWidget(add_here_btn, 1) - location_box_layout.addLayout(loc_header) - - self.project_location_combo = NoScrollComboBox() - self.project_location_combo.setObjectName(KEY_PROJECT_LOCATION) - self.project_location_combo.addItems(VALUES_PROJECT_LOCATION) - self.project_location_combo.currentTextChanged.connect(self.on_project_location_changed) - self.project_location_combo.hide() - + location_box_layout.addLayout(loc_header) group_container_layout.addWidget(location_box) # === Superstructure Section (Contains Everything) === @@ -1510,6 +826,10 @@ def _toggle_structure(checked): QPushButton:hover { background-color: #7a9a12; } + QPushButton:disabled{ + background: #D0D0D0; + color: #666; + } """) modify_geo_btn.clicked.connect(self.show_additional_inputs) add_geo_row.addWidget(modify_geo_btn, 1) @@ -1650,6 +970,10 @@ def _toggle_structure(checked): QPushButton:hover { background-color: #7a9a12; } + QPushButton:disabled{ + background: #f1f1f1; + color: #666; + } """) modify_mat_btn.clicked.connect(self.show_material_properties_dialog) mat_prop_header.addWidget(modify_mat_btn, 1) @@ -1721,7 +1045,7 @@ def _toggle_sub(checked): sub_group.setLayout(sub_layout) group_container_layout.addWidget(sub_group) - + group_container_layout.addStretch() scroll_area.setWidget(group_container) @@ -1814,14 +1138,8 @@ def show_additional_inputs(self): self.additional_inputs_window.activateWindow() self._set_additional_inputs_enabled(not self.is_locked) - def _toggle_lock_state(self): - self.is_locked = not self.is_locked - self._apply_lock_state() - def _apply_lock_state(self): - if self.lock_button: - icon_path = ":/vectors/lock_close.svg" if self.is_locked else ":/vectors/lock_open.svg" - self.lock_button.setIcon(QIcon(icon_path)) + self.update_lock_icon() enabled = not self.is_locked if self.scroll_area: diff --git a/src/osdagbridge/desktop/ui/utils/custom_titlebar.py b/src/osdagbridge/desktop/ui/utils/custom_titlebar.py new file mode 100644 index 00000000..be46a2e4 --- /dev/null +++ b/src/osdagbridge/desktop/ui/utils/custom_titlebar.py @@ -0,0 +1,168 @@ +from PySide6.QtWidgets import QWidget, QLabel, QToolButton, QHBoxLayout, QSizePolicy, QVBoxLayout +from PySide6.QtCore import Qt, QPoint, QEvent +from PySide6.QtGui import QMouseEvent, QFont + +class CustomTitleBar(QWidget): + def __init__(self, max_res_btn: bool = False, min_res_btn:bool = False, parent=None): + super().__init__(parent) + # Ensures automatic deletion when closed + self.setAttribute(Qt.WA_DeleteOnClose, True) + self._drag_pos = QPoint() + self.setObjectName("CustomTitleBar") + self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) + self.setFixedHeight(32) # Set consistent height + self.setAttribute(Qt.WA_StyledBackground, True) + + # Add Osdag logo icon to the title bar + from PySide6.QtSvgWidgets import QSvgWidget + self.logo_label = QSvgWidget(":/vectors/Osdag_logo.svg", self) + self.logo_label.setObjectName("LogoLabel") + self.logo_label.setFixedSize(20, 20) + + # Title label + self.title_label = QLabel("Osdag", self) + self.title_label.setObjectName("TitleLabel") + self.title_label.setAlignment(Qt.AlignVCenter | Qt.AlignLeft) + + # Set font for title + title_font = QFont() + title_font.setPointSize(9) + title_font.setWeight(QFont.Weight.Medium) + self.title_label.setFont(title_font) + + # Minimize button (optional) + self.btn_minimize = None + if min_res_btn: + self.btn_minimize = QToolButton(self) + self.btn_minimize.setObjectName("MinimizeButton") + self.btn_minimize.setToolTip("Minimize") + self.btn_minimize.setText("–") + self.btn_minimize.setFixedSize(46, 32) + self.btn_minimize.clicked.connect(self._minimize_parent) + + # Maximize/Restore button (optional) + self.btn_max_restore = None + if max_res_btn: + self.btn_max_restore = QToolButton(self) + self.btn_max_restore.setObjectName("MaxRestoreButton") + self.btn_max_restore.setToolTip("Maximize") + self.btn_max_restore.setText("□") + self.btn_max_restore.setFixedSize(46, 32) + self.btn_max_restore.clicked.connect(self._toggle_max_restore) + + # Close button + self.btn_close = QToolButton(self) + self.btn_close.setObjectName("CloseButton") + self.btn_close.setToolTip("Close") + self.btn_close.setText("✕") # Better multiplication symbol + self.btn_close.setFixedSize(46, 32) + self.btn_close.clicked.connect(self._close_parent) + + # Title bar layout + outer_layout = QVBoxLayout(self) + outer_layout.setContentsMargins(0, 0, 0, 0) + outer_layout.setSpacing(0) + + row_widget = QWidget(self) + row_layout = QHBoxLayout(row_widget) + row_layout.setContentsMargins(8, 0, 0, 0) + row_layout.setSpacing(8) + row_layout.addWidget(self.logo_label, 0) + row_layout.addWidget(self.title_label, 1) + if self.btn_minimize is not None: + row_layout.addWidget(self.btn_minimize, 0) + if self.btn_max_restore is not None: + row_layout.addWidget(self.btn_max_restore, 0) + row_layout.addWidget(self.btn_close, 0) + outer_layout.addWidget(row_widget) + + self.bottom_line = QWidget(self) + self.bottom_line.setObjectName("BottomLine") + self.bottom_line.setFixedHeight(1) + outer_layout.addWidget(self.bottom_line) + + # Keep the maximize/restore button state in sync with the window state + if self.btn_max_restore is not None and self.parent() is not None: + self.parent().installEventFilter(self) + self._update_max_restore_icon() + + def setTitle(self, title): + """Set the title displayed in the title bar.""" + self.title_label.setText(title) + + def _close_parent(self): + """Close the parent widget.""" + if self.parent(): + self.parent().close() + + def _minimize_parent(self): + """Minimize the parent widget.""" + if self.parent(): + self.parent().showMinimized() + + def _toggle_max_restore(self): + """Toggle between maximizing and restoring the parent window.""" + window = self.parent() + if not window: + return + if window.isMaximized(): + window.showNormal() + else: + window.showMaximized() + self._update_max_restore_icon() + + def _update_max_restore_icon(self): + """Update the icon/text and tooltip of the maximize/restore button based on window state.""" + if self.btn_max_restore is None: + return + window = self.parent() + if not window: + return + if window.isMaximized(): + self.btn_max_restore.setText("❐") # Restore + self.btn_max_restore.setToolTip("Restore") + else: + self.btn_max_restore.setText("□") # Maximize + self.btn_max_restore.setToolTip("Maximize") + + def eventFilter(self, obj, event): + # Update button when window state changes (e.g., via system controls or double-click) + if obj is self.parent() and event.type() == QEvent.WindowStateChange: + self._update_max_restore_icon() + return super().eventFilter(obj, event) + + def mousePressEvent(self, event: QMouseEvent): + """Handle mouse press for dragging.""" + if event.button() == Qt.LeftButton: + if self.parent() and self.parent().isWindow(): + self._drag_pos = event.globalPosition().toPoint() - self.parent().frameGeometry().topLeft() + event.accept() + + def mouseMoveEvent(self, event: QMouseEvent): + """Handle mouse move for dragging.""" + if (event.buttons() & Qt.LeftButton and + not self._drag_pos.isNull() and + self.parent() and + self.parent().isWindow()): + self.parent().move(event.globalPosition().toPoint() - self._drag_pos) + event.accept() + + def mouseReleaseEvent(self, event: QMouseEvent): + """Reset drag position on mouse release.""" + if event.button() == Qt.LeftButton: + self._drag_pos = QPoint() + event.accept() + + def mouseDoubleClickEvent(self, event: QMouseEvent): + """Handle double-click to maximize/restore window.""" + if event.button() == Qt.LeftButton and self.parent() and self.parent().isWindow(): + if self.parent().isMaximized(): + self.parent().showNormal() + else: + self.parent().showMaximized() + # Keep button state in sync + if self.btn_max_restore is not None: + self._update_max_restore_icon() + event.accept() + else: + super().mouseDoubleClickEvent(event) From 3e0476b4560fa23cd9bb12ed810ece6891270999 Mon Sep 17 00:00:00 2001 From: mhsuhail00 Date: Sat, 13 Dec 2025 00:23:18 +0530 Subject: [PATCH 47/59] -Added logs_dock, animation, dock_control, menubar Added logs_dock Added dock_control_icons beside menubar Added Options in menubar Added Animations related to docking undocking Added Input/Output labels --- .../desktop/resources/resources.qrc | 12 + .../desktop/resources/resources_rc.py | 709 ++++++++++++++++-- .../desktop/resources/themes/lightstyle.qss | 101 +++ .../vectors/input_dock_active_light.svg | 3 + .../vectors/input_dock_inactive_light.svg | 1 + .../resources/vectors/inputs_label_light.svg | 4 + .../resources/vectors/lock_open_light.svg | 1 + .../vectors/logs_dock_active_light.svg | 3 + .../vectors/logs_dock_inactive_light.svg | 1 + .../vectors/output_dock_active_light.svg | 3 + .../vectors/output_dock_inactive_light.svg | 1 + .../resources/vectors/outputs_label_light.svg | 4 + .../desktop/ui/docks/input_dock.py | 499 +++++++++++- src/osdagbridge/desktop/ui/docks/log_dock.py | 2 +- .../desktop/ui/docks/output_dock.py | 25 +- src/osdagbridge/desktop/ui/template_page.py | 661 ++++++++++++++-- 16 files changed, 1910 insertions(+), 120 deletions(-) create mode 100644 src/osdagbridge/desktop/resources/vectors/input_dock_active_light.svg create mode 100644 src/osdagbridge/desktop/resources/vectors/input_dock_inactive_light.svg create mode 100644 src/osdagbridge/desktop/resources/vectors/inputs_label_light.svg create mode 100644 src/osdagbridge/desktop/resources/vectors/lock_open_light.svg create mode 100644 src/osdagbridge/desktop/resources/vectors/logs_dock_active_light.svg create mode 100644 src/osdagbridge/desktop/resources/vectors/logs_dock_inactive_light.svg create mode 100644 src/osdagbridge/desktop/resources/vectors/output_dock_active_light.svg create mode 100644 src/osdagbridge/desktop/resources/vectors/output_dock_inactive_light.svg create mode 100644 src/osdagbridge/desktop/resources/vectors/outputs_label_light.svg diff --git a/src/osdagbridge/desktop/resources/resources.qrc b/src/osdagbridge/desktop/resources/resources.qrc index db511f3e..a876dcf2 100644 --- a/src/osdagbridge/desktop/resources/resources.qrc +++ b/src/osdagbridge/desktop/resources/resources.qrc @@ -20,5 +20,17 @@ vectors/lock_open.svg vectors/lock_close.svg vectors/Osdag_logo.svg + + vectors/inputs_label_light.svg + vectors/input_dock_active_light.svg + vectors/input_dock_inactive_light.svg + + vectors/logs_dock_active_light.svg + vectors/logs_dock_inactive_light.svg + + vectors/output_dock_active_light.svg + vectors/output_dock_inactive_light.svg + vectors/outputs_label_light.svg + diff --git a/src/osdagbridge/desktop/resources/resources_rc.py b/src/osdagbridge/desktop/resources/resources_rc.py index 845efa00..79a25e66 100644 --- a/src/osdagbridge/desktop/resources/resources_rc.py +++ b/src/osdagbridge/desktop/resources/resources_rc.py @@ -6,54 +6,87 @@ from PySide6 import QtCore qt_resource_data = b"\ -\x00\x00\x02\xe0\ +\x00\x00\x04\xe7\ \x00\ -\x00\x0b\xd8x\xda\xd5U\xdfO\xa3@\x10~7\xf1\x7f\ -\x98\xa4/zIm+\xb5\x171\xf7P*\xf6\x9a\xa0\ -U\xc13\xde\xdb\x02[\xd8\xb8e9\xd8\xfe\xf0\xcc\xfd\ -\xef\xb7P\xa0\x80\x14\xb4>\xb9\xfb\x02\xc3\xce7\xdf|\ -;3t\xbe\xc1\x8f\x0f\xad\xc3\x03\x00\x98\xea\x97\xc31\ -\x8c\x1f&\xa0\x1bO\x9a\xaa\xffTU\x03\xda0\xbd\x1f\ -\x0fo&\xbf\xd5KP\x9e@\xbfUG\x93\xab\xc9h\ -b<\xc5>\x1f\x0b\x03\xdf:\x87\x07\x87\x07\x9d\xbd\xe8\ -\xf5N`\xacM\x95\xa1\x96\xd0\x83#\x0d\xa3\x90\x83\xee\ -c\x8b\xcc\x88u\xbc/\xa1\xbbkD\xbcG\xe2\xd9l\ -\x05\xaf1\x08\x98\xc8zv\x02\xb6\xf0\xec\xb6\xc5(\x0b\ -dh\xcd\xfa\xd1\xbeH\xbe\xb3\xc0\xc6\xc2\xda\xf3\xd7\x10\ -2Jlh\x9dw\xd1\xac'%\xdf\xe7(p\x88'\ -C\xd7_'\x16\x1f\xd96\xf1\x9c\xd4\xf4/\xd2\xe1\xce\ -`\x8c\x1a\xc4\xaf\x89z\x15\xaf\x04#5v\xe3UG\ -ex\x95Q\xc9\x02\x9f\x8a\x03\xa7\x19\x9f\x19\xf3x;\ -$\x7f\xb1\xf0\xdcZ7`\xed\x00\xd9d\x11\xe6\xe9\xff\ -\xf1\x03\xe6\xe3\x80\xbf\xb4\x11%\x8e7\xc7\x1e\x97a\x18\ -=\xfe\x1a\x89g\x1clr\xba\xd3}J\xb8x\x95e\ -\x17y6\xc55\xa9]v\xa3\x9dj!jbL\x99\ -\x89(\x8c\x5cl=\x9bl\x0d:\x7f\x11\x00\xf1\x0d\xc5\ -6E\xd8^\xdf\xb2\xdf\x92L\x91%)M>\xf4\x91\ -\x15'?HU\xcf\xa0dY\xdc8\xb1\x10gA\x8a\ -\xba\x226w\x05\xe2 Ct1q\x5c^0U\xe8\ --\xc5\xabZBi\xebXQS\xf1\xdaMLv\xd9\ -\x12g\xf4*\x22\x9f\xc5\xab\x06\xc0\x8al\xd8\xae\xab\xeb\ -\x94\xc3{\x92#s\xe4\x08\xcd\x17\x01=\x92;Kl\ -\x89\x10a'\x89q\x12.\x9d\xe3\xdcu\xee\xd1\xe2\x92\ -hq\xf5F\xbd\x17=\xae<\x18\xc6\xf4&ku\x05\ -\x85\x184\xbc\xc4t\xff6\xbf]\x84\xae\xb2\xe0\x9cy\ -\xbb\xe5X\xb9\x84\xe3b9\x99T\x9c\xcaw\xcd*)\ -\x0a\x93Q\xfb\x02*o\xfd\xac\xa6\x5c\xf2xYs\x0a\ -\x07\xe8\xf53/\x8e\xd7|\xd3i2X\xb9\xfe*d\ -Q\xaa\x8e\xb7W[\x98\x03Ms\xa2$\xc0\x9b`~\ -\x80\xc3p[I\x15\xe2\xd4\xab\xb9[\x88M\xa8Gb\ -;\x98\xb7F\x8b\x90\xb3\xb9A8\xc5\x0a\x12\xc9A\xf3\ -@\x8ej_C&\xa6\xad\xd8-~,\xd1,\x8e\xcc\ -\xd2,.\xe2\xcb\xc0\x03\xe4\x89\xc1\x11\x08\xdd\x0b\xe8\x1a\ -sX\x01|\xb7S>t\xa1\xc1\xf2\x83\xab_\xfa\x13\ -l\x84n]\x13\x8f\xcc\xc5\x89\xa6R\xdd\x1d\xb1\xf2\xff\ -\xd0m\x1e\xf2yr\x83\xfa\xe3\x0d\xac\x1bKs\xd6\x8b\ -\xf6;\x80JeW\x01\x85\x06\xd1\xae\x84B\xeb{,\ -\xca)\xf8jZ\x96x\x7fF\xcd2\xd4g\xf4\x1cQ\ -\x16~1)s\x94\x1bU\xc4\xdf\xfb\x96d\xed\xea\xde\ -Z\xe8fU\xad\xaet~j&\xf3$\x99u\x0a\x13\ -\xces\x8dx\xf8=C\x5c8\xfe\x07\x97\xd7n\xe4\ +\x00\x15\xe8x\xda\xd5X]O\xdb<\x14\xbeG\xe2?\ +X\xea\xc5>\xb4\xae-\x85\x8e\x05\xbd\x17\xfd\x82!\x15\ +\x18\xb4\xdb\xc4n\x90\x93\xb8\xad\x85\x1bg\x8eC\xe1\x9d\ +\xf8\xef\xef\xb1\x9b\xef&n`\xef\xcdb\x0du\xb6s\ +\xce\xe3\xc7\x8f\xcf9N\xeb=\xfa\xe7E\xcf\xfe\x1eB\ +\xe8j:\xea\x9f\xa1\xb3o\xe7h:\xbb\x9d\x8c\xa7_\ +\xc6\xe3\x19j\xa2\xab\x9b\xb3\xfe\xe5\xf9\xcf\xf1\x08\x0dn\ +\xd1\xf4\xebxx~z><\x9f\xdd\xeaw^\xe6\x06\ +\xbdo\xed\xef\xed\xef\xb5^\x05\xaf\xf3\x11\x9dM\xae\x06\ +\xfdI\x04\x0f\xbd\x9d\x10\x1cH4\xf5\x89C\xe7\xd4y\ +\xf7Z@\xd7\x17\x98z?\xa8\xe7\xf25\xfa\xad\x8d \ +\x1b;\xf7\x0b\xc1C\xcfm:\x9cqa\xa1\xc6\xfcP\ +\xb5\x93h\x9c\x0b\x97@o\xc7\x7fD\x01g\xd4E\x8d\ +\xcfm<\xeft\xa3\xf1\x15\x16\x0b\xeaY\xa8\xed?F\ +=>v]\xea-\xe2\xaeg\xc5\xc3\xf5\x8cs6\xa3\ +\xbe\xc1\xeb\xa9~\x22\x1bqg[?&(\xfd\xd3\x04\ +J\xe2\xf8\x00&\x1c$x\xe6\xdc\x93\xcd\x80\xfeK\xe0\ +\xcd\xb4wc\xac)\xb0K\xc3 \x0b\xff\x97/\xb8O\ +\x84|jbF\x17\xde\x8ax\xd2B}\xf5\xf3\xfb\x10\ +~\x13\xb1Y\xd3\xf5\xd4gT\xc2\x7f-k\x89=\x97\ +\x11\xc3\xd2Fm\xd5b.@\x13g\x8c\xdb\x98\xa1\xe1\ +\x928\xf76\x7fDS\xf9\x04\x06\xf4\x0e\xe9\xbe\x01\xf4\ +\xfd\xdeF\x9f\x82\x8c-w\xbb\xf1\xe2\x03\x1f;z\xf1\ +\xbd\x98\xf5\xc4\x94e\xc1\x8eS\x07K.b\xabk\xea\ +\xca%X\xec%\x16\x97\x84.\x962\xd7U\xc2wW\ +?\xe5\x14v\xd3\x17K4\xa5\x9fj`\xd6\x92?\x90\ +\x04^\x89\xe7#\xfd\x18\x0c8\xaa\x8f\xb8&]\xc7\x18\ +\xea,\x8e\xae\xf0\x028\x0f\x05{k\xb5\x1e\x88\x03.\ +\x82V\xe4\xe3c\xf0\xb0x\x97H\xfb\x82x\xe1\x00\x8b\ +\x86$+\x9faI\xee|x\xf3\x0et\x13\xde\xd9X\ +\x98\x14\x7f\xa8\x9aI\xf1\xdbGi\x877 \x04\x06b\ +\x9f\xc9\xebG\xb0\xc8\x8czR,\x16\x92\x02{\xa0\x1d\ +\x01\xd26\x9d\x8c\xba\xae\xad\x800 \xabl\x1f\xb2g\ +\xbc\xb69_\x90 \xa8\xb06>V\xad\x965\xa4\xc7\ +\xebF\x9f\x12m$G\xb8\x84\xa1CC\xe8\xab\x85\xab\ +z\xd7L\xe2\xc8F\x86\xce+\xfc\x19\xb6*\xa1e\xdc\ +;m\x17h)\x9c\xf7\xfa>\x03\x022\xcb\x06\xa1$\ +\xe4\x94\x0a\xb3\x01\x9e\xa1\xe5\xd2L\x93\x91\xb9\xb42\xb1\ +=\xea\x16\x1bC[\xfd\x92\xfb\xd9\xd0\x1e\xf5\xda\x5cJ\ +\xbez\xf9&i/M,D\x9a=\xa38z\xbc\x15\ +F\x8f\xd3\xdc\x07\xf1~\x04)\x05r\xae\x87\x94I\x14\ +\xa4\xd1\xbe\x5c\x95\x9bXU3\xebeephN\xc2\ +\x06\xad\x1d\xab\x08\xd13\xe4\x97\x18\x86\xc7=RH\xfd\ +\x9dR\x17\xe6H\x90[B&@\x97K\xed \xc7\xe6\ ++*\xaa.TT\xe3\xcb\xf1\x0d\x94T\x83o\xb3\xd9\ +\xd5eRY\x0dp@\xd0\x84<\x10\xf6\xfa\xaa\xeak\ +\x18,\x07!\x88\xca\xab>J\xeb%\x90\x92_\xaf\xcd\ +`Vv\x17\xd7\x91xl\xce\xdc\x13T\xca\xc4\x91!\ +;g\xed\xe5C\x7f\xaa\x0bI\x1e\xe5\xa6\xb0\xb1\x90\x93\ +)gr\xab($\xe3\xed\xb0\x90\xdb\xbd]\x02-\x10\ +\xb0\xe5\xac\x10\xe2K\xc81\xb3YM\xc4\xc6\xd5\x0f\xea\ +.\x88l\x0c\xc3\x00N\xfd\x8cJF\x06*-\xa3\xdd\ +\xf5\xaf\x0a\x0d\x13l\x13\xd6\xd0\xaf\xe9\x9f\x05\x98\xc6|\ +m\xce\xb5\xa9\xf5\x09_\xf0\x9cqs\x82.\xadg\xb6\ +\xc3@Zxo\x88n\x5cP\x8f\xae`\xc6.\xa9V\ +{,-\xc7\xdb\xbbk\xea,\xb8\x9ey\xfa\x0e\xd4;\ +\xa59\xef\xa8V\xc3Pee\x91\x98\xc2=\xd5JM\ +\xe1\xc7\x1b\x02r\x12\x7f\x1b\x97\x05\xdc\x7f\xc2f\xd1\xd4\ +\x9f\xf09d<\xf8\xcb\xa8\xcc@\xde\xc9\x22\xf9t\xe8\ +t\x9d\xaa\xd3k4\xbd\x9bU\xa7\xdd\xfd|`G\xf1\ +$\x8au\x03]\xe1L\xa8G\xea\x04\xf1g\x95U+\ +\x12\x1c\x84\xa6\xa09\xe2\xce\xbd!\x09\xea\x1c\x18yf\ +0\xff\xce\x85\xf9\xe8\xba\x22\xa2\xa5%\xf7\x81j\xb5\xae\ +?\xdd\xfc\x96\xe5\xf2d\xd5%\xff\xb9\x14\xd4\x0c\xf2\xdf\ +\xd8\xa5\xd2\x84\xebX\xb5\x9aW\x01\xedw\x8eW\x94=\ +Y\xe8\xcd\x90\x87\x82\x82\x14.\xc9\xfa\xcd\x07\xb4\xe2\x1e\ +W\xd7rb\xfc\x10Q\xa7\xdc/_\xcb\xd4\x11\x9c1\ +Hf\x16\xc8O\xc25\x98\x95_\x93\xda\xaa\x9d \xa8\ +\x9c&\x8a7\xb4\x10\xe4\x09\xc0\x08$\x97\x04\x05\xda\x88\ +\xaar\xe1|\x81U\xb5\x99\xa5\x95m\xe6CO\xf2\xaf\ +[uhL[\x90\xc2\x8e\xbe\x9c\x98\xe1\xf7\xdb\xaai\ +\xf8\x17\x04\xac\xaf\xaa\xf0G\x9fa\xe2\x05\xac\xa0\xd2\x8f\ +\xab\xf1n\xfb\xff\x05Zu\xe2\x01\xee\xa7\xb6j\x1a\xee\ +\x08\x8b{\x98\xa5\xe1B`\xdb\xbc\x13\xe3\xce\xa0\xdd\xe9\ +\x1d\x14\xd2dp\x98\x13\xff\x1fr\xc3Ah\xe7\x87\x8b\ +\x17,\xb5z\x05\xe8\x0buI\xa0\xbd\x87~K\xdfG\ +\xf4e&\xa8\x8fB\xdd\x8b\x0c(r\xc3e\xf4\xe8\xdb\ +C\x1e\x0a\x04t\x8cl\x22\xd7\x84x1+\xf0\xb7\x00\ +\xed??\x04\x81\x7f\ \x00\x00\x00\xb2\ <\ svg xmlns=\x22http:\ @@ -68,6 +101,43 @@ 1 367-367 57 57-\ 424 424Z\x22/>\ +\x00\x00\x02-\ +<\ +svg width=\x2240\x22 h\ +eight=\x2240\x22 viewB\ +ox=\x220 0 40 40\x22 f\ +ill=\x22none\x22 xmlns\ +=\x22http://www.w3.\ +org/2000/svg\x22>\x0d\x0a\ +\x0d\x0a\x0d\x0a\ \x00\x00\x0e;\ <\ svg width=\x22149\x22 \ @@ -348,6 +418,41 @@ V-636ZM226.67-14\ 6.67v-422.66 422\ .66Z\x22/>\ +\x00\x00\x02\x07\ +<\ +svg width=\x2240\x22 h\ +eight=\x2240\x22 viewB\ +ox=\x220 0 40 40\x22 f\ +ill=\x22none\x22 xmlns\ +=\x22http://www.w3.\ +org/2000/svg\x22>\x0d\x0a\ +\x0d\x0a\x0d\x0a\ \x00\x00\x021\ <\ svg xmlns=\x22http:\ @@ -386,6 +491,35 @@ .33 236 97.33ZM4\ 80-480Z\x22/>\ \ +\x00\x00\x01\xa4\ +<\ +svg xmlns=\x22http:\ +//www.w3.org/200\ +0/svg\x22 height=\x224\ +0px\x22 viewBox=\x220 \ +-960 960 960\x22 wi\ +dth=\x2240px\x22 fill=\ +\x22#000000\x22>\ \x00\x00\x02\xd3\ <\ svg xmlns=\x22http:\ @@ -521,6 +655,139 @@ 133 20.0001 6.11\ 133Z\x22 fill=\x22blac\ k\x22/>\x0d\x0a\x0d\x0a\ +\x00\x00\x08+\ +<\ +svg width=\x2236\x22 h\ +eight=\x22922\x22 view\ +Box=\x220 0 36 922\x22\ + fill=\x22none\x22 xml\ +ns=\x22http://www.w\ +3.org/2000/svg\x22>\ +\x0d\x0a\x0d\x0a\ +\x0d\x0a\x0d\x0a\ \x00\x00\x02\xde\ <\ svg xmlns=\x22http:\ @@ -607,6 +874,174 @@ .33 236 97.33ZM4\ 80-480Z\x22/>\ \ +\x00\x00\x0aV\ +<\ +svg width=\x2236\x22 h\ +eight=\x22918\x22 view\ +Box=\x220 0 36 918\x22\ + fill=\x22none\x22 xml\ +ns=\x22http://www.w\ +3.org/2000/svg\x22>\ +\x0d\x0a\x0d\x0a\ +\x0d\x0a\x0d\x0a\ \x00\x00\x05O\ <\ svg width=\x2240\x22 h\ @@ -740,6 +1175,100 @@ 9.83Zm-293.33 60\ 8v-586.66 586.66\ Z\x22/>\ +\x00\x00\x02/\ +<\ +svg width=\x2240\x22 h\ +eight=\x2240\x22 viewB\ +ox=\x220 0 40 40\x22 f\ +ill=\x22none\x22 xmlns\ +=\x22http://www.w3.\ +org/2000/svg\x22>\x0d\x0a\ +\x0d\x0a\x0d\x0a\ +\x00\x00\x01\x9d\ +<\ +svg xmlns=\x22http:\ +//www.w3.org/200\ +0/svg\x22 height=\x224\ +0px\x22 viewBox=\x220 \ +-960 960 960\x22 wi\ +dth=\x2240px\x22 fill=\ +\x22#000000\x22>\ +\x00\x00\x01\xa2\ +<\ +svg xmlns=\x22http:\ +//www.w3.org/200\ +0/svg\x22 height=\x224\ +0px\x22 viewBox=\x220 \ +-960 960 960\x22 wi\ +dth=\x2240px\x22 fill=\ +\x22#000000\x22>\ \x00\x00\x025\ <\ svg xmlns=\x22http:\ @@ -797,6 +1326,11 @@ \x01d\x8d\x87\ \x00c\ \x00h\x00e\x00c\x00k\x00e\x00d\x00.\x00s\x00v\x00g\ +\x00\x1b\ +\x0aa\xaa'\ +\x00i\ +\x00n\x00p\x00u\x00t\x00_\x00d\x00o\x00c\x00k\x00_\x00a\x00c\x00t\x00i\x00v\x00e\ +\x00_\x00l\x00i\x00g\x00h\x00t\x00.\x00s\x00v\x00g\ \x00\x0e\ \x06\xb1\x9b\x07\ \x00O\ @@ -805,11 +1339,21 @@ \x0d\xd6\xeag\ \x00l\ \x00o\x00c\x00k\x00_\x00c\x00l\x00o\x00s\x00e\x00.\x00s\x00v\x00g\ +\x00\x1a\ +\x0b}\x5cG\ +\x00l\ +\x00o\x00g\x00s\x00_\x00d\x00o\x00c\x00k\x00_\x00a\x00c\x00t\x00i\x00v\x00e\x00_\ +\x00l\x00i\x00g\x00h\x00t\x00.\x00s\x00v\x00g\ \x00\x13\ \x0cPg\xa7\ \x00a\ \x00r\x00r\x00o\x00w\x00_\x00d\x00o\x00w\x00n\x00_\x00d\x00a\x00r\x00k\x00.\x00s\ \x00v\x00g\ +\x00\x1e\ +\x08a\xb9\xc7\ +\x00o\ +\x00u\x00t\x00p\x00u\x00t\x00_\x00d\x00o\x00c\x00k\x00_\x00i\x00n\x00a\x00c\x00t\ +\x00i\x00v\x00e\x00_\x00l\x00i\x00g\x00h\x00t\x00.\x00s\x00v\x00g\ \x00\x0a\ \x0f\xec\x03\xe7\ \x00d\ @@ -819,6 +1363,11 @@ \x00a\ \x00r\x00r\x00o\x00w\x00_\x00u\x00p\x00_\x00l\x00i\x00g\x00h\x00t\x00.\x00s\x00v\ \x00g\ +\x00\x16\ +\x0e\x16='\ +\x00i\ +\x00n\x00p\x00u\x00t\x00s\x00_\x00l\x00a\x00b\x00e\x00l\x00_\x00l\x00i\x00g\x00h\ +\x00t\x00.\x00s\x00v\x00g\ \x00\x0d\ \x005w\xc7\ \x00l\ @@ -828,6 +1377,11 @@ \x00a\ \x00r\x00r\x00o\x00w\x00_\x00d\x00o\x00w\x00n\x00_\x00l\x00i\x00g\x00h\x00t\x00.\ \x00s\x00v\x00g\ +\x00\x17\ +\x0d\xa0\xcd'\ +\x00o\ +\x00u\x00t\x00p\x00u\x00t\x00s\x00_\x00l\x00a\x00b\x00e\x00l\x00_\x00l\x00i\x00g\ +\x00h\x00t\x00.\x00s\x00v\x00g\ \x00\x11\ \x03\xf9t'\ \x00a\ @@ -838,6 +1392,21 @@ \x00d\ \x00e\x00s\x00i\x00g\x00n\x00_\x00r\x00e\x00p\x00o\x00r\x00t\x00.\x00s\x00v\x00g\ \ +\x00\x1c\ +\x0ap\xe4'\ +\x00o\ +\x00u\x00t\x00p\x00u\x00t\x00_\x00d\x00o\x00c\x00k\x00_\x00a\x00c\x00t\x00i\x00v\ +\x00e\x00_\x00l\x00i\x00g\x00h\x00t\x00.\x00s\x00v\x00g\ +\x00\x1c\ +\x01\xd8[\xc7\ +\x00l\ +\x00o\x00g\x00s\x00_\x00d\x00o\x00c\x00k\x00_\x00i\x00n\x00a\x00c\x00t\x00i\x00v\ +\x00e\x00_\x00l\x00i\x00g\x00h\x00t\x00.\x00s\x00v\x00g\ +\x00\x1d\ +\x0e\xaf\xb9\xe7\ +\x00i\ +\x00n\x00p\x00u\x00t\x00_\x00d\x00o\x00c\x00k\x00_\x00i\x00n\x00a\x00c\x00t\x00i\ +\x00v\x00e\x00_\x00l\x00i\x00g\x00h\x00t\x00.\x00s\x00v\x00g\ \x00\x08\ \x08\xc8U\xe7\ \x00s\ @@ -847,34 +1416,50 @@ qt_resource_struct = b"\ \x00\x00\x00\x00\x00\x02\x00\x00\x00\x02\x00\x00\x00\x01\ \x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x14\x00\x02\x00\x00\x00\x01\x00\x00\x00\x0e\ +\x00\x00\x00\x14\x00\x02\x00\x00\x00\x01\x00\x00\x00\x16\ \x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x02\x00\x00\x00\x0b\x00\x00\x00\x03\ +\x00\x00\x00\x00\x00\x02\x00\x00\x00\x13\x00\x00\x00\x03\ \x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x01\x18\x00\x00\x00\x00\x00\x01\x00\x00\x1f:\ +\x00\x00\x02\x02\x00\x00\x00\x00\x00\x01\x00\x00/T\ \x00\x00\x01\x9b\x13E\x17;\ -\x00\x00\x00H\x00\x00\x00\x00\x00\x01\x00\x00\x02\xe4\ +\x00\x00\x00H\x00\x00\x00\x00\x00\x01\x00\x00\x04\xeb\ \x00\x00\x01\x9b\x13E\x179\ -\x00\x00\x018\x00\x00\x00\x00\x00\x01\x00\x00\x22\x1c\ +\x00\x00\x02\x22\x00\x00\x00\x00\x00\x01\x00\x0026\ \x00\x00\x01\x9b\x13E\x179\ -\x00\x00\x01f\x00\x00\x00\x00\x00\x01\x00\x00$Q\ +\x00\x00\x03\x12\x00\x00\x00\x00\x00\x01\x00\x00I\x0a\ +\x00\x00\x01\x9a\xfc\x9f}A\ +\x00\x00\x02\x84\x00\x00\x00\x00\x00\x01\x00\x00>\xc5\ \x00\x00\x01\x9b\x13E\x179\ -\x00\x00\x00d\x00\x00\x00\x00\x00\x01\x00\x00\x03\x9a\ -\x00\x00\x01\x9a\xfc\x9f}\x0c\ -\x00\x00\x01\x8e\x00\x00\x00\x00\x00\x01\x00\x00)\xa4\ +\x00\x00\x00\xa0\x00\x00\x00\x00\x00\x01\x00\x00\x07\xd2\ +\x00\x00\x01\x9b\x13\xd0_\xa8\ +\x00\x00\x01J\x00\x00\x00\x00\x00\x01\x00\x00\x1dS\ +\x00\x00\x01\x9a\xfc\x9f}M\ +\x00\x00\x02\xac\x00\x00\x00\x00\x00\x01\x00\x00D\x18\ \x00\x00\x01\x9b\x13E\x179\ -\x00\x00\x01\xb6\x00\x00\x00\x00\x00\x01\x00\x00,c\ +\x00\x00\x03\x90\x00\x00\x00\x00\x00\x01\x00\x00LQ\ \x00\x00\x01\x9b\x13E\x17;\ -\x00\x00\x00\xee\x00\x00\x00\x00\x00\x01\x00\x00\x19\xe7\ +\x00\x00\x01\xa6\x00\x00\x00\x00\x00\x01\x00\x00!\xd2\ \x00\x00\x01\x9b\x13E\x179\ -\x00\x00\x00\xa8\x00\x00\x00\x00\x00\x01\x00\x00\x14\xdb\ +\x00\x00\x00d\x00\x00\x00\x00\x00\x01\x00\x00\x05\xa1\ +\x00\x00\x01\x9a\xfc\x9f};\ +\x00\x00\x02\xd4\x00\x00\x00\x00\x00\x01\x00\x00F\xd7\ +\x00\x00\x01\x9a\xfc\x9f}K\ +\x00\x00\x00\xe4\x00\x00\x00\x00\x00\x01\x00\x00\x19\x13\ +\x00\x00\x01\x9a\xfc\x9f}A\ +\x00\x00\x01\x1e\x00\x00\x00\x00\x00\x01\x00\x00\x1b\x1e\ \x00\x00\x01\x9b\x13E\x179\ -\x00\x00\x00\x86\x00\x00\x00\x00\x00\x01\x00\x00\x11\xd9\ +\x00\x00\x02P\x00\x00\x00\x00\x00\x01\x00\x004k\ +\x00\x00\x01\x9b\x07\x002e\ +\x00\x00\x00\xc2\x00\x00\x00\x00\x00\x01\x00\x00\x16\x11\ \x00\x00\x01\x9b\x13E\x179\ -\x00\x00\x00\xd4\x00\x00\x00\x00\x00\x01\x00\x00\x17\x10\ +\x00\x00\x01\xd0\x00\x00\x00\x00\x00\x01\x00\x00'%\ +\x00\x00\x01\x9b\x07\x002a\ +\x00\x00\x03P\x00\x00\x00\x00\x00\x01\x00\x00J\xab\ +\x00\x00\x01\x9a\xfc\x9f}<\ +\x00\x00\x01\x8c\x00\x00\x00\x00\x00\x01\x00\x00\x1e\xfb\ \x00\x00\x01\x9b\x13E\x179\ \x00\x00\x00&\x00\x01\x00\x00\x00\x01\x00\x00\x00\x00\ -\x00\x00\x01\x9b\x13e\xa4S\ +\x00\x00\x01\x9b\x13\xd50\xdc\ " def qInitResources(): diff --git a/src/osdagbridge/desktop/resources/themes/lightstyle.qss b/src/osdagbridge/desktop/resources/themes/lightstyle.qss index 71146c11..452eeeb6 100644 --- a/src/osdagbridge/desktop/resources/themes/lightstyle.qss +++ b/src/osdagbridge/desktop/resources/themes/lightstyle.qss @@ -47,6 +47,71 @@ QCheckBox::indicator:checked { image: url(:/vectors/checked.svg); } +QMenuBar#template_page_menu_bar { + background-color: #F4F4F4; + color: #000000; + padding: 0px; +} +QMenuBar#template_page_menu_bar::item { + padding: 5px 10px; + background: transparent; + border-radius: 0px; +} +QMenuBar#template_page_menu_bar::item:selected { + background: #FFFFFF; +} +QMenuBar#template_page_menu_bar::item:pressed { + background: #E8E8E8; +} +QMenuBar#template_page_menu_bar QMenu { + background-color: #FFFFFF; + border: 1px solid #D0D0D0; + border-radius: 4px; + padding: 0px; +} +QMenuBar#template_page_menu_bar QMenu::item { + padding: 5px; + color: #000000; + font-size: 11px; +} +QMenuBar#template_page_menu_bar QMenu::item:selected { + background-color: #E6F0FF; + border-radius: 3px; +} +QMenuBar#template_page_menu_bar QMenu::separator { + height: 1px; + background: #F0F0F0; + margin-left: 2px; + margin-right: 2px; + margin-top: 0px; + margin-bottom: 0px; +} +QMenuBar#template_page_menu_bar QMenu::right-arrow { + width: 8px; + height: 8px; +} + +/* Dropdown menu style */ +QMenu { + background: #fff; + border: 1px solid #90AF13; + font-size: 14px; + padding: 0px; +} + +QMenu::item { + padding: 8px 16px; + color: #333; + border: none; + margin: 1px; +} + +QMenu::item:selected { + background: #90AF13; + color: #fff; + border-radius: 2px; +} + /* ============================================== 3. GENERAL BUTTON STYLES (Base Level) ============================================== */ @@ -140,3 +205,39 @@ QToolButton#CloseButton:pressed { QWidget#BottomLine { background-color: #90AF13; } +/*=======================Logs-Dock===========================*/ +QWidget#logs_dock QLabel { + background-color: #F2F2F2; + color: #000000; + padding: 3px; + font-weight: bold; + font-size: 12px; +} +QWidget#logs_dock QTextEdit { + background-color: #F8F8F8; + border: 1px solid #D0D0D0; + font-family: 'Courier New', monospace; + font-size: 12px; + padding: 5px; + color: #000000; +} +QWidget#logs_dock QScrollBar:vertical { + background: #E0E0E0; /* Light grey for the scrollbar track */ + width: 8px; + margin: 0px 0px 0px 3px; + border-radius: 2px; +} +QWidget#logs_dock QScrollBar::handle:vertical { + background: #A0A0A0; /* Medium grey for the scrollbar handle */ + min-height: 30px; + border-radius: 2px; +} +QWidget#logs_dock QScrollBar::handle:vertical:hover { + background: #707070; /* Darker grey on hover for the handle */ +} +QWidget#logs_dock QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical { + height: 0px; /* Hides the up/down arrows */ +} +QWidget#logs_dock QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { + background: none; /* Hides the area between handle and arrows */ +} diff --git a/src/osdagbridge/desktop/resources/vectors/input_dock_active_light.svg b/src/osdagbridge/desktop/resources/vectors/input_dock_active_light.svg new file mode 100644 index 00000000..70151046 --- /dev/null +++ b/src/osdagbridge/desktop/resources/vectors/input_dock_active_light.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/osdagbridge/desktop/resources/vectors/input_dock_inactive_light.svg b/src/osdagbridge/desktop/resources/vectors/input_dock_inactive_light.svg new file mode 100644 index 00000000..32bfa48d --- /dev/null +++ b/src/osdagbridge/desktop/resources/vectors/input_dock_inactive_light.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/osdagbridge/desktop/resources/vectors/inputs_label_light.svg b/src/osdagbridge/desktop/resources/vectors/inputs_label_light.svg new file mode 100644 index 00000000..4306a92a --- /dev/null +++ b/src/osdagbridge/desktop/resources/vectors/inputs_label_light.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/osdagbridge/desktop/resources/vectors/lock_open_light.svg b/src/osdagbridge/desktop/resources/vectors/lock_open_light.svg new file mode 100644 index 00000000..5f22789a --- /dev/null +++ b/src/osdagbridge/desktop/resources/vectors/lock_open_light.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/osdagbridge/desktop/resources/vectors/logs_dock_active_light.svg b/src/osdagbridge/desktop/resources/vectors/logs_dock_active_light.svg new file mode 100644 index 00000000..5d23c855 --- /dev/null +++ b/src/osdagbridge/desktop/resources/vectors/logs_dock_active_light.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/osdagbridge/desktop/resources/vectors/logs_dock_inactive_light.svg b/src/osdagbridge/desktop/resources/vectors/logs_dock_inactive_light.svg new file mode 100644 index 00000000..56e27348 --- /dev/null +++ b/src/osdagbridge/desktop/resources/vectors/logs_dock_inactive_light.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/osdagbridge/desktop/resources/vectors/output_dock_active_light.svg b/src/osdagbridge/desktop/resources/vectors/output_dock_active_light.svg new file mode 100644 index 00000000..7ef0439c --- /dev/null +++ b/src/osdagbridge/desktop/resources/vectors/output_dock_active_light.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/osdagbridge/desktop/resources/vectors/output_dock_inactive_light.svg b/src/osdagbridge/desktop/resources/vectors/output_dock_inactive_light.svg new file mode 100644 index 00000000..a8e65d4a --- /dev/null +++ b/src/osdagbridge/desktop/resources/vectors/output_dock_inactive_light.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/osdagbridge/desktop/resources/vectors/outputs_label_light.svg b/src/osdagbridge/desktop/resources/vectors/outputs_label_light.svg new file mode 100644 index 00000000..cfddf439 --- /dev/null +++ b/src/osdagbridge/desktop/resources/vectors/outputs_label_light.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/osdagbridge/desktop/ui/docks/input_dock.py b/src/osdagbridge/desktop/ui/docks/input_dock.py index 73e8eab3..245d1350 100644 --- a/src/osdagbridge/desktop/ui/docks/input_dock.py +++ b/src/osdagbridge/desktop/ui/docks/input_dock.py @@ -146,8 +146,479 @@ def apply_field_style(widget): class MaterialPropertiesDialog(QDialog): - # ... (Keep all MaterialPropertiesDialog code unchanged) - pass + MEMBER_OPTIONS = ["Girder", "Cross Bracing", "End Diaphragm", "Deck"] + STEEL_MEMBERS = {"Girder", "Cross Bracing", "End Diaphragm"} + + def __init__(self, parent=None): + super().__init__(parent) + self.setWindowTitle("Material Properties") + self.setMinimumWidth(580) + self.setStyleSheet("background-color: white;") + + self.parent_dock = parent + self._loading = False + self.current_member = None + self.member_data = {} + + self.member_combo = NoScrollComboBox() + self.member_combo.addItems(self.MEMBER_OPTIONS) + apply_field_style(self.member_combo) + + self.material_combo = NoScrollComboBox() + apply_field_style(self.material_combo) + + main_layout = QVBoxLayout(self) + main_layout.setContentsMargins(20, 16, 20, 16) + + # Create a container widget for all form fields + form_container = QWidget() + form_layout = QVBoxLayout(form_container) + form_layout.setContentsMargins(0, 0, 0, 0) + form_layout.setSpacing(10) + + # Member row + member_row = QHBoxLayout() + member_row.setContentsMargins(0, 0, 0, 0) + member_row.setSpacing(18) + member_label = QLabel("Member*:") + member_label.setStyleSheet("font-size: 12px; color: #2d2d2d;") + member_label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) + member_label.setFixedWidth(280) + self.member_combo.setFixedWidth(242) + member_row.addWidget(member_label) + member_row.addWidget(self.member_combo) + member_row.addStretch() + form_layout.addLayout(member_row) + + # Material row + material_row = QHBoxLayout() + material_row.setContentsMargins(0, 0, 0, 0) + material_row.setSpacing(18) + material_label = QLabel("Material*:") + material_label.setStyleSheet("font-size: 12px; color: #2d2d2d;") + material_label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) + material_label.setFixedWidth(280) + self.material_combo.setFixedWidth(242) + material_row.addWidget(material_label) + material_row.addWidget(self.material_combo) + material_row.addStretch() + form_layout.addLayout(material_row) + + main_layout.addWidget(form_container) + + self.stack = QStackedWidget() + self.stack.setContentsMargins(0, 0, 0, 0) + self.steel_page = self._build_steel_form() + self.deck_page = self._build_deck_form() + self.stack.addWidget(self.steel_page) + self.stack.addWidget(self.deck_page) + main_layout.addWidget(self.stack) + + # Updated default row with proper alignment + default_row = QHBoxLayout() + default_row.setContentsMargins(0, 0, 0, 0) + default_row.setSpacing(18) + default_label = QLabel("Default") + default_label.setStyleSheet("font-size: 12px; color: #2d2d2d;") + default_label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) + default_label.setFixedWidth(280) + self.default_checkbox = QCheckBox() + # Create container for checkbox to align it to the left + checkbox_container = QWidget() + checkbox_layout = QHBoxLayout(checkbox_container) + checkbox_layout.setContentsMargins(0, 0, 0, 0) + checkbox_layout.setSpacing(0) + checkbox_layout.addWidget(self.default_checkbox) + checkbox_layout.addStretch() + + default_row.addWidget(default_label) + default_row.addWidget(checkbox_container) + main_layout.addLayout(default_row) + + self.member_combo.currentTextChanged.connect(self._on_member_changed) + self.material_combo.currentTextChanged.connect(self._on_material_changed) + self.default_checkbox.stateChanged.connect(self._on_default_toggled) + + self._initialize_member_data() + self._on_member_changed(self.member_combo.currentText()) + + def closeEvent(self, event): + self._save_current_member_form() + super().closeEvent(event) + + def _build_steel_form(self): + widget = QWidget() + layout = QVBoxLayout(widget) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(10) + self.steel_field_inputs = {} + for label_text in STEEL_MEMBER_FIELDS: + row = QHBoxLayout() + row.setContentsMargins(0, 0, 0, 0) + row.setSpacing(18) + label = QLabel(label_text) + label.setStyleSheet("font-size: 12px; color: #2d2d2d;") + label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) + label.setFixedWidth(280) + line_edit = QLineEdit() + line_edit.setFixedWidth(242) + apply_field_style(line_edit) + # Add validator for 1 decimal place + line_edit.setValidator(QDoubleValidator(0.0, 99999.0, 1)) + line_edit.textEdited.connect(self._handle_user_override) + self.steel_field_inputs[label_text] = line_edit + row.addWidget(label) + row.addWidget(line_edit) + row.addStretch() + layout.addLayout(row) + layout.addStretch() + return widget + + def _build_deck_form(self): + widget = QWidget() + layout = QVBoxLayout(widget) + layout.setSpacing(10) + self.deck_field_inputs = {} + for label_text in DECK_MEMBER_FIELDS: + row = QHBoxLayout() + row.setSpacing(18) + label = QLabel(label_text) + label.setStyleSheet("font-size: 12px; color: #2d2d2d;") + label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) + label.setFixedWidth(280) + if label_text == "Ecm Multiplication Factor": + self.deck_factor_combo = NoScrollComboBox() + self.deck_factor_combo.addItems(ECM_FACTOR_LABELS) + self.deck_factor_combo.setFixedWidth(242) + apply_field_style(self.deck_factor_combo) + self.deck_factor_combo.currentTextChanged.connect(self._on_factor_changed) + + self.deck_factor_custom_input = QLineEdit() + apply_field_style(self.deck_factor_custom_input) + self.deck_factor_custom_input.setPlaceholderText("Custom factor") + self.deck_factor_custom_input.setFixedWidth(242) + self.deck_factor_custom_input.setVisible(False) + self.deck_factor_custom_input.setEnabled(False) + self.deck_factor_custom_input.setValidator(QDoubleValidator(0.1, 5.0, 1)) + self.deck_factor_custom_input.textEdited.connect(self._handle_user_override) + + row.addWidget(label) + row.addWidget(self.deck_factor_combo) + row.addStretch() + + # Add custom input row (hidden by default) + custom_row = QHBoxLayout() + custom_row.setContentsMargins(0, 0, 0, 0) + custom_row.setSpacing(18) + custom_label = QLabel("") # Empty label for alignment + custom_label.setFixedWidth(280) + custom_row.addWidget(custom_label) + custom_row.addWidget(self.deck_factor_custom_input) + custom_row.addStretch() + layout.addLayout(custom_row) + + self.deck_field_inputs[label_text] = self.deck_factor_combo + else: + line_edit = QLineEdit() + line_edit.setFixedWidth(242) + apply_field_style(line_edit) + # Add validator for 1 decimal place + line_edit.setValidator(QDoubleValidator(0.0, 99999.0, 1)) + line_edit.textEdited.connect(self._handle_user_override) + row.addWidget(label) + row.addWidget(line_edit) + row.addStretch() + self.deck_field_inputs[label_text] = line_edit + layout.addLayout(row) + layout.addStretch() + return widget + + def _initialize_member_data(self): + for member in self.MEMBER_OPTIONS: + material = self._get_parent_grade(member) + fields = self._default_fields_for_member(member, material) + self.member_data[member] = { + "material": material, + "fields": fields, + "is_default": True, + "factor_label": DEFAULT_ECM_FACTOR_LABEL if member == "Deck" else None, + "custom_factor": "1.0" if member == "Deck" else None, + } + + def _default_fields_for_member(self, member, material=None, factor_label=None, custom_factor=None): + if member == "Deck": + grade = material or self._get_parent_grade(member) or (VALUES_DECK_CONCRETE_GRADE[0] if VALUES_DECK_CONCRETE_GRADE else "") + factor_label = factor_label or DEFAULT_ECM_FACTOR_LABEL + factor_value = self._factor_value_from_label(factor_label, custom_factor) + return self._deck_defaults(grade, factor_value) + grade = material or self._get_parent_grade(member) or (VALUES_MATERIAL[0] if VALUES_MATERIAL else "") + return self._steel_defaults(grade) + + def _steel_defaults(self, grade): + grade_value = self._extract_numeric_grade(grade) + defaults = STEEL_GRADE_BASE_VALUES.get(grade_value, STEEL_GRADE_BASE_VALUES[250]) + return { + "Ultimate Tensile Strength, Fu (MPa)": "{:.1f}".format(defaults["Fu"]), + "Yield Strength, Fy (MPa)": "{:.1f}".format(defaults["Fy"]), + "Modulus of Elasticity, E (GPa)": "{:.1f}".format(STEEL_MODULUS_E_GPA), + "Modulus of Rigidity, G (GPa)": "{:.1f}".format(STEEL_MODULUS_G_GPA), + "Poisson's Ratio, ν": "{:.1f}".format(STEEL_POISSON_RATIO), + "Thermal Expansion Coefficient, (×10⁻⁶/°C)": "{:.1f}".format(STEEL_THERMAL_COEFF), + } + + def _deck_defaults(self, grade, factor_value): + strength = self._extract_numeric_grade(grade, default=25) + fck = float(strength) + fctm = round(0.7 * math.sqrt(fck), 1) + ecm = round(5.0 * math.sqrt(fck) * factor_value, 1) + return { + "Characteristic Compressive (Cube) Strength of Concrete, (fck)cu (MPa)": "{:.1f}".format(fck), + "Mean Tensile Strength of Concrete, fctm (MPa)": "{:.1f}".format(fctm), + "Secant Modulus of Elasticity of Concrete, Ecm (GPa)": "{:.1f}".format(ecm), + "Ecm Multiplication Factor": "{:.1f}".format(factor_value), + } + + def _extract_numeric_grade(self, grade, default=250): + digits = ''.join(ch for ch in grade if ch.isdigit()) + try: + return int(digits) if digits else default + except ValueError: + return default + + def _materials_for_member(self, member): + return VALUES_DECK_CONCRETE_GRADE if member == "Deck" else VALUES_MATERIAL + + def _on_member_changed(self, member): + if self.current_member: + self._save_current_member_form() + + self.current_member = member + is_deck = member == "Deck" + self.stack.setCurrentWidget(self.deck_page if is_deck else self.steel_page) + + data = self.member_data.get(member) + if not data: + self.member_data[member] = self._create_default_entry(member) + data = self.member_data[member] + + if data.get("is_default"): + self._apply_defaults_for_member(member, update_ui=False) + + materials = self._materials_for_member(member) + self._loading = True + self.material_combo.clear() + self.material_combo.addItems(materials) + if data["material"] in materials: + self.material_combo.setCurrentText(data["material"]) + elif materials: + self.material_combo.setCurrentIndex(0) + data["material"] = self.material_combo.currentText() + + self.default_checkbox.setChecked(data.get("is_default", False)) + if is_deck: + self._populate_deck_fields(data) + else: + self._populate_steel_fields(data) + self._loading = False + + def _populate_steel_fields(self, data): + for label, widget in self.steel_field_inputs.items(): + value = data["fields"].get(label, "") + # Format to 1 decimal place + try: + formatted_value = "{:.1f}".format(float(value)) + widget.setText(formatted_value) + except (ValueError, TypeError): + widget.setText(value) + + def _populate_deck_fields(self, data): + for label, widget in self.deck_field_inputs.items(): + if label == "Ecm Multiplication Factor": + factor_label = data.get("factor_label", DEFAULT_ECM_FACTOR_LABEL) + if factor_label not in ECM_FACTOR_LABELS: + factor_label = DEFAULT_ECM_FACTOR_LABEL + self.deck_factor_combo.blockSignals(True) + self.deck_factor_combo.setCurrentText(factor_label) + self.deck_factor_combo.blockSignals(False) + self._update_custom_factor_visibility(factor_label) + self.deck_factor_custom_input.blockSignals(True) + custom_val = data.get("custom_factor", "1.0") + try: + formatted_custom = "{:.1f}".format(float(custom_val)) + self.deck_factor_custom_input.setText(formatted_custom) + except (ValueError, TypeError): + self.deck_factor_custom_input.setText(custom_val) + self.deck_factor_custom_input.blockSignals(False) + else: + value = data["fields"].get(label, "") + # Format to 1 decimal place + try: + formatted_value = "{:.1f}".format(float(value)) + widget.setText(formatted_value) + except (ValueError, TypeError): + widget.setText(value) + + def _save_current_member_form(self): + if not self.current_member: + return + data = self.member_data.setdefault(self.current_member, self._create_default_entry(self.current_member)) + data["material"] = self.material_combo.currentText() + if self.current_member == "Deck": + for label, widget in self.deck_field_inputs.items(): + if label == "Ecm Multiplication Factor": + data["factor_label"] = self.deck_factor_combo.currentText() + data["custom_factor"] = self.deck_factor_custom_input.text() or "1.0" + else: + data["fields"][label] = widget.text() + factor_value = self._factor_value_from_label(data["factor_label"], data.get("custom_factor")) + data["fields"]["Ecm Multiplication Factor"] = "{:.1f}".format(factor_value) + else: + for label, widget in self.steel_field_inputs.items(): + data["fields"][label] = widget.text() + data["is_default"] = self.default_checkbox.isChecked() + + def _create_default_entry(self, member): + material = self._get_parent_grade(member) + return { + "material": material, + "fields": self._default_fields_for_member(member, material), + "is_default": True, + "factor_label": DEFAULT_ECM_FACTOR_LABEL if member == "Deck" else None, + "custom_factor": "1.0" if member == "Deck" else None, + } + + def _apply_defaults_for_member(self, member, update_ui=True): + data = self.member_data.setdefault(member, self._create_default_entry(member)) + grade = self._get_parent_grade(member) or data.get("material") + materials = self._materials_for_member(member) + if grade not in materials and materials: + grade = materials[0] + data["material"] = grade + if member == "Deck": + data["factor_label"] = DEFAULT_ECM_FACTOR_LABEL + data["custom_factor"] = "1.0" + factor_value = self._factor_value_from_label(DEFAULT_ECM_FACTOR_LABEL) + data["fields"] = self._deck_defaults(grade, factor_value) + else: + data["fields"] = self._steel_defaults(grade) + data["is_default"] = True + + if update_ui and member == self.current_member: + self._loading = True + self.material_combo.setCurrentText(grade) + if member == "Deck": + self._populate_deck_fields(data) + else: + self._populate_steel_fields(data) + self.default_checkbox.setChecked(True) + self._loading = False + + def _factor_value_from_label(self, label, custom_factor=None): + for text, value in ECM_FACTOR_OPTIONS: + if text == label: + if value is None: + try: + return float(custom_factor) if custom_factor else 1.0 + except ValueError: + return 1.0 + return value + return 1.0 + + def _reset_current_member_to_defaults(self): + if not self.current_member: + return + + self._apply_defaults_for_member(self.current_member, update_ui=False) + data = self.member_data.get(self.current_member) + if not data: + return + + target_material = data.get("material", "") + self._loading = True + if target_material: + index = self.material_combo.findText(target_material) + if index >= 0: + self.material_combo.setCurrentIndex(index) + elif self.material_combo.count() > 0: + self.material_combo.setCurrentIndex(0) + data["material"] = self.material_combo.currentText() + if self.current_member == "Deck": + self._populate_deck_fields(data) + else: + self._populate_steel_fields(data) + self._loading = False + + self.default_checkbox.blockSignals(True) + self.default_checkbox.setChecked(True) + self.default_checkbox.blockSignals(False) + self._save_current_member_form() + + def _update_custom_factor_visibility(self, label): + is_custom = label == CUSTOM_ECM_FACTOR_LABEL + self.deck_factor_custom_input.setVisible(is_custom) + self.deck_factor_custom_input.setEnabled(is_custom) + self.deck_factor_combo.setVisible(not is_custom) + + def _on_material_changed(self, material): + if self._loading: + return + data = self.member_data.get(self.current_member) + if data: + data["material"] = material + self._handle_user_override() + + def _on_default_toggled(self, state): + if self._loading: + return + try: + check_state = Qt.CheckState(state) + except ValueError: + check_state = Qt.CheckState.Checked if bool(state) else Qt.CheckState.Unchecked + if check_state == Qt.CheckState.Checked: + self._reset_current_member_to_defaults() + else: + data = self.member_data.get(self.current_member) + if data: + data["is_default"] = False + + def _on_factor_changed(self, label): + self._update_custom_factor_visibility(label) + self._handle_user_override() + + def _handle_user_override(self): + if self._loading: + return + if self.default_checkbox.isChecked(): + self._loading = True + self.default_checkbox.setChecked(False) + self._loading = False + data = self.member_data.get(self.current_member) + if data: + data["is_default"] = False + self._save_current_member_form() + + def _get_parent_grade(self, member): + parent = self.parent_dock + if not parent: + return "" + mapping = { + "Girder": getattr(parent, "girder_combo", None), + "Cross Bracing": getattr(parent, "cross_bracing_combo", None), + "End Diaphragm": getattr(parent, "end_diaphragm_combo", None), + "Deck": getattr(parent, "deck_combo", None), + } + combo = mapping.get(member) + return combo.currentText() if combo else "" + + def set_member(self, member): + index = self.member_combo.findText(member) + if index >= 0: + self.member_combo.setCurrentIndex(index) + + def sync_with_parent_defaults(self): + for member, data in self.member_data.items(): + if data.get("is_default"): + self._apply_defaults_for_member(member, update_ui=(member == self.current_member)) class InputDock(QWidget): @@ -195,6 +666,7 @@ def __init__(self, backend, parent): self.toggle_btn.setCursor(Qt.CursorShape.PointingHandCursor) self.toggle_btn.setFixedSize(6, 60) self.toggle_btn.setToolTip("Hide panel") + self.toggle_btn.clicked.connect(self.toggle_input_dock) self.toggle_btn.setStyleSheet(""" QPushButton { background-color: #6c8408; @@ -308,10 +780,33 @@ def update_lock_icon(self): self.lock_btn.setIcon(QIcon(":/vectors/lock_close.svg")) else: self.lock_btn.setIcon(QIcon(":/vectors/lock_open.svg")) + + def resizeEvent(self, event): + super().resizeEvent(event) + # Checking hasattr is only meant to prevent errors, + # while standalone testing of this widget + if self.parent: + if self.width() == 0: + if hasattr(self.parent, 'update_docking_icons'): + self.parent.update_docking_icons(input_is_active=False) + elif self.width() > 0: + if hasattr(self.parent, 'update_docking_icons'): + self.parent.update_docking_icons(input_is_active=True) + def paintEvent(self, event): self.update_lock_icon() return super().paintEvent(event) + + def toggle_input_dock(self): + parent = self.parent + if hasattr(parent, 'toggle_animate'): + is_collapsing = self.width() > 0 + parent.toggle_animate(show=not is_collapsing, dock='input') + + self.toggle_btn.setText("❯" if is_collapsing else "❮") + self.toggle_btn.setToolTip("Show panel" if is_collapsing else "Hide panel") + # Lock-Tooltip-Events-Ends------------------------------------------------------------------------- diff --git a/src/osdagbridge/desktop/ui/docks/log_dock.py b/src/osdagbridge/desktop/ui/docks/log_dock.py index 5c16189b..9e0ac748 100644 --- a/src/osdagbridge/desktop/ui/docks/log_dock.py +++ b/src/osdagbridge/desktop/ui/docks/log_dock.py @@ -18,7 +18,7 @@ def __init__(self, parent=None): def init_ui(self): # Create layout for the log dock layout = QVBoxLayout(self) - layout.setContentsMargins(2, 2, 2, 0) + layout.setContentsMargins(5, 2, 5, 0) layout.setSpacing(0) # Create a top strip for "Log Window" diff --git a/src/osdagbridge/desktop/ui/docks/output_dock.py b/src/osdagbridge/desktop/ui/docks/output_dock.py index 34fc3cf1..b678dc40 100644 --- a/src/osdagbridge/desktop/ui/docks/output_dock.py +++ b/src/osdagbridge/desktop/ui/docks/output_dock.py @@ -85,11 +85,33 @@ def apply_field_style(widget): class OutputDock(QWidget): """Output dock with collapsible design controls and scrollable layout.""" - def __init__(self): + def __init__(self, parent): super().__init__() + self.parent = parent self.setStyleSheet("background: transparent;") self.init_ui() + def toggle_output_dock(self): + parent = self.parent + if hasattr(parent, 'toggle_animate'): + is_collapsing = self.width() > 0 + parent.toggle_animate(show=not is_collapsing, dock='output') + + self.toggle_btn.setText("❮" if is_collapsing else "❯") + self.toggle_btn.setToolTip("Show panel" if is_collapsing else "Hide panel") + + def resizeEvent(self, event): + super().resizeEvent(event) + # Checking hasattr is only meant to prevent errors + if self.parent: + if self.width() == 0: + if hasattr(self.parent, 'update_docking_icons'): + self.parent.update_docking_icons(output_is_active=False) + elif self.width() > 0: + if hasattr(self.parent, 'update_docking_icons'): + self.parent.update_docking_icons(output_is_active=True) + + def init_ui(self): # Main horizontal layout to hold toggle strip and content self.main_layout = QHBoxLayout(self) @@ -109,6 +131,7 @@ def init_ui(self): self.toggle_btn.setCursor(Qt.CursorShape.PointingHandCursor) self.toggle_btn.setFixedSize(6, 60) self.toggle_btn.setToolTip("Hide panel") + self.toggle_btn.clicked.connect(self.toggle_output_dock) self.toggle_btn.setStyleSheet(""" QPushButton { background-color: #7a9a12; diff --git a/src/osdagbridge/desktop/ui/template_page.py b/src/osdagbridge/desktop/ui/template_page.py index 0bb59ec4..262cdf37 100644 --- a/src/osdagbridge/desktop/ui/template_page.py +++ b/src/osdagbridge/desktop/ui/template_page.py @@ -3,9 +3,9 @@ QApplication, QWidget, QVBoxLayout, QHBoxLayout, QLabel, QMenuBar, QSplitter, QSizePolicy, QPushButton, QScrollArea, QFrame, ) -from PySide6.QtCore import Qt -from PySide6.QtCore import Qt, QFile, QTextStream -from PySide6.QtGui import QIcon +from PySide6.QtSvgWidgets import QSvgWidget +from PySide6.QtCore import Qt, QFile, QTextStream, Signal +from PySide6.QtGui import QIcon, QAction, QKeySequence from osdagbridge.desktop.ui.docks.input_dock import InputDock from osdagbridge.desktop.ui.docks.output_dock import OutputDock @@ -20,6 +20,8 @@ class DummyCADWidget(QWidget): def __init__(self): super().__init__() layout = QVBoxLayout(self) + layout.setContentsMargins(5, 2, 5, 0) + layout.setSpacing(0) label = QLabel("CAD Window\n(Placeholder)") label.setAlignment(Qt.AlignCenter) label.setStyleSheet( @@ -67,62 +69,613 @@ def __init__(self, title: str, backend: object, parent=None): self.init_ui() def init_ui(self): + # Docking icons Parent class + class ClickableSvgWidget(QSvgWidget): + clicked = Signal() # Define a custom clicked signal + def __init__(self, parent=None): + super().__init__(parent) + self.setCursor(Qt.CursorShape.PointingHandCursor) + + def mousePressEvent(self, event): + if event.button() == Qt.MouseButton.LeftButton: + self.clicked.emit() # Emit the clicked signal on left-click + super().mousePressEvent(event) + main_v_layout = QVBoxLayout(self) main_v_layout.setContentsMargins(0, 0, 0, 0) main_v_layout.setSpacing(0) + menu_h_layout = QHBoxLayout() + menu_h_layout.setContentsMargins(0, 0, 0, 0) + menu_h_layout.setSpacing(0) + self.menu_bar = QMenuBar(self) + self.menu_bar.setObjectName("template_page_menu_bar") self.menu_bar.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed) self.menu_bar.setFixedHeight(28) self.menu_bar.setContentsMargins(0, 0, 0, 0) - self.menu_bar.addMenu("File") - self.menu_bar.addMenu("Edit") - self.menu_bar.addMenu("Graphics") - self.menu_bar.addMenu("Help") - main_v_layout.addWidget(self.menu_bar) - - body_widget = QWidget() - body_layout = QHBoxLayout(body_widget) - body_layout.setContentsMargins(0, 0, 0, 0) - body_layout.setSpacing(0) - - # Main horizontal splitter - main_splitter = QSplitter(Qt.Horizontal, body_widget) - - # Input dock - input_dock = InputDock(backend=self.backend, parent=self) - input_dock.setMinimumWidth(380) - input_dock.setMaximumWidth(450) - main_splitter.addWidget(input_dock) - - # CAD widget - cad_widget = DummyCADWidget() - main_splitter.addWidget(cad_widget) - - # Output dock - output_dock = OutputDock() - output_dock.setMinimumWidth(380) - output_dock.setMaximumWidth(450) - main_splitter.addWidget(output_dock) - - body_layout.addWidget(main_splitter) - - # Set stretch factors for main splitter - main_splitter.setStretchFactor(0, 0) # Input dock - fixed - main_splitter.setStretchFactor(1, 1) # Central area - expandable - main_splitter.setStretchFactor(2, 0) # Output dock - fixed - - # Set initial sizes - input_dock_width = 350 - output_dock_width = 280 - total_width = self.width() if self.width() > 0 else 1200 - central_width = max(500, total_width - input_dock_width - output_dock_width) - main_splitter.setSizes([input_dock_width, central_width, output_dock_width]) - - main_v_layout.addWidget(body_widget) - - # Store references - self.main_splitter = main_splitter - self.input_dock = input_dock - self.output_dock = output_dock - self.cad_widget = cad_widget + menu_h_layout.addWidget(self.menu_bar) + + # Control buttons + control_btn_widget = QWidget() + control_btn_widget.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) + control_btn_widget.setObjectName("control_btn_widget") + control_button_layout = QHBoxLayout(control_btn_widget) + control_button_layout.setSpacing(10) + control_button_layout.setContentsMargins(5,5,5,5) + + self.input_dock_control = ClickableSvgWidget() + self.input_dock_control.setFixedSize(18, 18) + self.input_dock_control.load(":/vectors/input_dock_active_light.svg") + self.input_dock_control.clicked.connect(self.input_dock_toggle) + self.input_dock_active = True + control_button_layout.addWidget(self.input_dock_control) + + self.log_dock_control = ClickableSvgWidget() + self.log_dock_control.load(":/vectors/logs_dock_inactive_light.svg") + self.log_dock_control.setFixedSize(18, 18) + self.log_dock_control.clicked.connect(self.logs_dock_toggle) + self.log_dock_active = False + control_button_layout.addWidget(self.log_dock_control) + + self.output_dock_control = ClickableSvgWidget() + self.output_dock_control.load(":/vectors/output_dock_inactive_light.svg") + self.output_dock_control.setFixedSize(18, 18) + self.output_dock_control.clicked.connect(self.output_dock_toggle) + self.output_dock_active = False + control_button_layout.addWidget(self.output_dock_control) + + menu_h_layout.addWidget(control_btn_widget) + main_v_layout.addLayout(menu_h_layout) + self.create_menu_bar_items() + + self.body_widget = QWidget() + self.layout = QHBoxLayout(self.body_widget) + self.layout.setContentsMargins(0, 0, 0, 0) + self.layout.setSpacing(0) + + self.splitter = QSplitter(Qt.Horizontal, self.body_widget) + self.splitter.setHandleWidth(2) + self.input_dock = InputDock(backend=self.backend, parent=self) + input_dock_width = self.input_dock.sizeHint().width() + self._input_dock_default_width = input_dock_width + self.splitter.addWidget(self.input_dock) + + central_widget = QWidget() + central_H_layout = QHBoxLayout(central_widget) + + # Add dock indicator labels + self.input_dock_label = InputDockIndicator(parent=self) + self.input_dock_label.setVisible(False) + central_H_layout.setContentsMargins(0, 0, 0, 0) + central_H_layout.setSpacing(0) + central_H_layout.addWidget(self.input_dock_label, 1) + + central_V_layout = QVBoxLayout() + central_V_layout.setContentsMargins(0, 0, 0, 0) + central_V_layout.setSpacing(0) + + # Add cad component checkboxes + self.cad_comp_widget = DummyCADWidget() + central_V_layout.addWidget(self.cad_comp_widget) + + self.cad_log_splitter = QSplitter(Qt.Vertical) + self.cad_log_splitter.setHandleWidth(2) + # Add Cad Model Widget + self.cad_log_splitter.addWidget(self.cad_comp_widget) + + self.logs_dock = LogDock() + self.logs_dock.setVisible(False) + # log text + self.textEdit = self.logs_dock.log_display + self.cad_log_splitter.addWidget(self.logs_dock) + + # Prefer stretch factors so ratio persists on resize + self.cad_log_splitter.setStretchFactor(0, 8) + self.cad_log_splitter.setStretchFactor(1, 1) + # Seed an initial 8:1 split; will be refined after first show + self.cad_log_splitter.setSizes([8, 1]) + + central_V_layout.addWidget(self.cad_log_splitter) + central_H_layout.addLayout(central_V_layout, 6) + + # Add output dock indicator label + self.output_dock_label = OutputDockIndicator(parent=self) + self.output_dock_label.setVisible(True) + central_H_layout.addWidget(self.output_dock_label, 1) + self.splitter.addWidget(central_widget) + + # root is the greatest level of parent that is the MainWindow + self.output_dock = OutputDock(parent=self) + self.splitter.addWidget(self.output_dock) + # self.output_dock.setStyleSheet(self.output_dock.styleSheet()) + self.output_dock.hide() + + self.layout.addWidget(self.splitter) + + total_width = self.width() - self.splitter.contentsMargins().left() - self.splitter.contentsMargins().right() + target_sizes = [0] * self.splitter.count() + target_sizes[0] = input_dock_width + target_sizes[2] = 0 + remaining_width = total_width - input_dock_width + target_sizes[1] = max(0, remaining_width) + self.splitter.setSizes(target_sizes) + self.layout.activate() + main_v_layout.addWidget(self.body_widget) + + #---------------------------------Docking-Icons-Functionality-START---------------------------------------------- + + def input_dock_toggle(self): + self.input_dock.toggle_input_dock() + + def output_dock_toggle(self): + self.output_dock.toggle_output_dock() + + def logs_dock_toggle(self): + self.log_dock_active = not self.log_dock_active + self.logs_dock.setVisible(self.log_dock_active) + if self.log_dock_active: + self.log_dock_control.load(":/vectors/logs_dock_active_light.svg") + else: + self.log_dock_control.load(":/vectors/logs_dock_inactive_light.svg") + + def update_docking_icons(self, input_is_active=None, log_is_active=None, output_is_active=None): + + if(input_is_active is not None): + self.input_dock_active = input_is_active + # Update and save control state + self.input_dock_active = input_is_active + if self.input_dock_active: + self.input_dock_control.load(":/vectors/input_dock_active_light.svg") + else: + self.input_dock_control.load(":/vectors/input_dock_inactive_light.svg") + + # Update output dock icon + if(output_is_active is not None): + # Update and save control state + self.output_dock_active = output_is_active + if self.output_dock_active: + self.output_dock_control.load(":/vectors/output_dock_active_light.svg") + else: + self.output_dock_control.load(":/vectors/output_dock_inactive_light.svg") + + # Update log dock icon + if(log_is_active is not None): + self.log_dock_active = log_is_active + # Update and save control state + self.logs_dock_active = log_is_active + if self.log_dock_active: + self.log_dock_control.load(":/vectors/logs_dock_active_light.svg") + else: + self.log_dock_control.load(":/vectors/logs_dock_inactive_light.svg") + + def toggle_animate(self, show: bool, dock: str = 'output', on_finished=None): + sizes = self.splitter.sizes() + n = self.splitter.count() + if dock == 'input': + dock_index = 0 + + elif dock == 'output': + dock_index = n - 1 + elif dock == 'log': + self.logs_dock.setVisible(show) + if on_finished: + on_finished() + return + else: + print(f"[Error] Invalid dock: {dock}") + return + + dock_widget = self.splitter.widget(dock_index) + if show: + dock_widget.show() + + self.splitter.setMinimumWidth(0) + self.splitter.setCollapsible(dock_index, True) + for i in range(n): + self.splitter.widget(i).setMinimumWidth(0) + self.splitter.widget(i).setMaximumWidth(16777215) + + target_sizes = sizes[:] + total_width = self.width() - self.splitter.contentsMargins().left() - self.splitter.contentsMargins().right() + input_dock = self.splitter.widget(0) + output_dock = self.splitter.widget(n - 1) + + if dock == 'input': + if show: + target_sizes[0] = input_dock.sizeHint().width() + self.input_dock_label.setVisible(False) + else: + target_sizes[0] = 0 + self.input_dock_label.setVisible(True) + target_sizes[2] = sizes[2] + remaining_width = total_width - target_sizes[0] - target_sizes[2] + target_sizes[1] = max(0, remaining_width) + else: + if show: + target_sizes[2] = output_dock.sizeHint().width() + self.output_dock_label.setVisible(False) + else: + target_sizes[2] = 0 + self.output_dock_label.setVisible(True) + target_sizes[0] = sizes[0] + remaining_width = total_width - target_sizes[0] - target_sizes[2] + target_sizes[1] = max(0, remaining_width) + + if sizes == target_sizes: + if not show: + dock_widget.hide() + if on_finished: + on_finished() + return + + def after_anim(): + self.finalize_dock_toggle(show, dock_widget, target_sizes) + if on_finished: + on_finished() + + # User requested "one step animation" with "no delay" + self.animate_splitter_sizes( + self.splitter, + sizes, + target_sizes, + duration=0, + on_finished=after_anim + ) + + def animate_splitter_sizes(self, splitter, start_sizes, end_sizes, duration, on_finished=None): + if duration <= 0: + # Instant update + splitter.setSizes(end_sizes) + splitter.refresh() + if splitter.parentWidget() and splitter.parentWidget().layout(): + splitter.parentWidget().layout().activate() + splitter.update() + if splitter.parentWidget(): + splitter.parentWidget().update() + self.update() + for i in range(splitter.count()): + widget = splitter.widget(i) + if widget: + widget.update() + + if on_finished: + on_finished() + return + + # Target 60 FPS -> ~16ms interval + interval = 16 + steps = max(1, duration // interval) + + current_step = 0 + + def ease_out_quad(t): + return t * (2 - t) + + def update_step(): + nonlocal current_step + if current_step <= steps: + progress = current_step / steps + # Apply easing + eased_progress = ease_out_quad(progress) + + sizes = [ + int(start + (end - start) * eased_progress) + for start, end in zip(start_sizes, end_sizes) + ] + + splitter.setSizes(sizes) + splitter.refresh() + if splitter.parentWidget() and splitter.parentWidget().layout(): + splitter.parentWidget().layout().activate() + splitter.update() + if splitter.parentWidget(): + splitter.parentWidget().update() + self.update() + for i in range(splitter.count()): + widget = splitter.widget(i) + if widget: + widget.update() + + current_step += 1 + else: + timer.stop() + if on_finished: + on_finished() + + timer = QTimer(self) + timer.timeout.connect(update_step) + timer.start(interval) + self._splitter_anim = timer + + def finalize_dock_toggle(self, show, dock_widget, target_sizes): + self.splitter.setSizes(target_sizes) + if not show: + dock_widget.hide() + self.splitter.refresh() + self.splitter.parentWidget().layout().activate() + self.splitter.update() + self.splitter.parentWidget().update() + self.update() + for i in range(self.splitter.count()): + self.splitter.widget(i).update() + + #---------------------------------Docking-Icons-Functionality-END---------------------------------------------- + + def resizeEvent(self, event): + + """Override resizeEvent with safety check.""" + # Check if being deleted + if not self.isVisible() or self.signalsBlocked(): + return + + # Check if splitter exists and has children + try: + if not hasattr(self, 'splitter') or self.splitter is None: + return + if self.splitter.count() < 3: + return + + if self.input_dock.isVisible(): + input_dock_width = self.input_dock.sizeHint().width() + else: + input_dock_width = 0 + + if self.output_dock.isVisible(): + output_dock_width = self.output_dock.sizeHint().width() + else: + output_dock_width = 0 + total_width = self.width() - self.splitter.contentsMargins().left() - self.splitter.contentsMargins().right() + self.splitter.setMinimumWidth(0) + self.splitter.setCollapsible(0, True) + self.splitter.setCollapsible(1, True) + self.splitter.setCollapsible(2, True) + for i in range(self.splitter.count()): + self.splitter.widget(i).setMinimumWidth(0) + self.splitter.widget(i).setMaximumWidth(16777215) + target_sizes = [0] * self.splitter.count() + target_sizes[0] = input_dock_width + target_sizes[2] = output_dock_width + remaining_width = total_width - input_dock_width - output_dock_width + target_sizes[1] = max(0, remaining_width) + self.splitter.setSizes(target_sizes) + self.splitter.refresh() + self.body_widget.layout().activate() + self.splitter.update() + super().resizeEvent(event) + + except (IndexError, RuntimeError, AttributeError): + # Being deleted, ignore + return + + def create_menu_bar_items(self): + # File Menus + file_menu = self.menu_bar.addMenu("File") + + load_input_action = QAction("Load Input", self) + load_input_action.setShortcut(QKeySequence("Ctrl+L")) + file_menu.addAction(load_input_action) + + file_menu.addSeparator() + + save_input_action = QAction("Save Input", self) + save_input_action.setShortcut(QKeySequence("Ctrl+S")) + file_menu.addAction(save_input_action) + + save_log_action = QAction("Save Log Messages", self) + save_log_action.setShortcut(QKeySequence("Alt+M")) + file_menu.addAction(save_log_action) + + create_report_action = QAction("Create Design Report", self) + create_report_action.setShortcut(QKeySequence("Alt+C")) + file_menu.addAction(create_report_action) + + file_menu.addSeparator() + + save_3d_action = QAction("Save 3D Model", self) + save_3d_action.setShortcut(QKeySequence("Alt+3")) + file_menu.addAction(save_3d_action) + + save_cad_action = QAction("Save CAD Image", self) + save_cad_action.setShortcut(QKeySequence("Alt+I")) + file_menu.addAction(save_cad_action) + + file_menu.addSeparator() + + quit_action = QAction("Quit", self) + quit_action.setShortcut(QKeySequence("Shift+Q")) + file_menu.addAction(quit_action) + + # Edit Menus + edit_menu = self.menu_bar.addMenu("Edit") + + design_prefs_action = QAction("Additional Inputs", self) + design_prefs_action.setShortcut(QKeySequence("Alt+P")) + edit_menu.addAction(design_prefs_action) + + graphics_menu = self.menu_bar.addMenu("Graphics") + zoom_in_action = QAction("Zoom In", self) + zoom_in_action.setShortcut(QKeySequence("Ctrl+I")) + graphics_menu.addAction(zoom_in_action) + + zoom_out_action = QAction("Zoom Out", self) + zoom_out_action.setShortcut(QKeySequence("Ctrl+O")) + graphics_menu.addAction(zoom_out_action) + + pan_action = QAction("Pan", self) + pan_action.setShortcut(QKeySequence("Ctrl+P")) + graphics_menu.addAction(pan_action) + + rotate_3d_action = QAction("Rotate 3D Model", self) + rotate_3d_action.setShortcut(QKeySequence("Ctrl+R")) + graphics_menu.addAction(rotate_3d_action) + + graphics_menu.addSeparator() + + front_view_action = QAction("Show Front View", self) + front_view_action.setShortcut(QKeySequence("Alt+Shift+F")) + graphics_menu.addAction(front_view_action) + + top_view_action = QAction("Show Top View", self) + top_view_action.setShortcut(QKeySequence("Alt+Shift+T")) + graphics_menu.addAction(top_view_action) + + side_view_action = QAction("Show Side View", self) + side_view_action.setShortcut(QKeySequence("Alt+Shift+S")) + graphics_menu.addAction(side_view_action) + + # Database Menu + database_menu = self.menu_bar.addMenu("Database") + + input_csv_action = QAction("Save Inputs (.csv)", self) + database_menu.addAction(input_csv_action) + + output_csv_action = QAction("Save Outputs (.csv)", self) + database_menu.addAction(output_csv_action) + + input_osi_action = QAction("Save Inputs (.osi)", self) + database_menu.addAction(input_osi_action) + + download_database_menu = database_menu.addMenu("Download Database") + + download_column_action = QAction("Column", self) + download_database_menu.addAction(download_column_action) + + download_bolt_action = QAction("Beam", self) + download_database_menu.addAction(download_bolt_action) + + download_weld_action = QAction("Channel", self) + download_database_menu.addAction(download_weld_action) + + download_angle_action = QAction("Angle", self) + download_database_menu.addAction(download_angle_action) + + database_menu.addSeparator() + + reset_action = QAction("Reset", self) + reset_action.setShortcut(QKeySequence("Alt+R")) + database_menu.addAction(reset_action) + + # Help Menu + help_menu = self.menu_bar.addMenu("Help") + + video_tutorials_action = QAction("Video Tutorials", self) + help_menu.addAction(video_tutorials_action) + + design_examples_action = QAction("Design Examples", self) + help_menu.addAction(design_examples_action) + + help_menu.addSeparator() + + ask_question_action = QAction("Ask Us a Question", self) + help_menu.addAction(ask_question_action) + + about_osdag_action = QAction("About Osdag", self) + help_menu.addAction(about_osdag_action) + + help_menu.addSeparator() + + check_update_action = QAction("Check For Update", self) + help_menu.addAction(check_update_action) + + +class InputDockIndicator(QWidget): + def __init__(self, parent): + super().__init__(parent) + # Ensures automatic deletion when closed + self.setAttribute(Qt.WA_DeleteOnClose, True) + self.parent = parent + self.setObjectName("input_dock_indicator") + self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Expanding) # Fixed width, expanding height + + input_layout = QHBoxLayout(self) + input_layout.setContentsMargins(6,0,0,0) + input_layout.setSpacing(0) + + self.input_label = QSvgWidget(":/vectors/inputs_label_light.svg") + input_layout.addWidget(self.input_label) + self.input_label.setFixedWidth(32) + + self.toggle_strip = QWidget() + self.toggle_strip.setObjectName("toggle_strip") + self.toggle_strip.setFixedWidth(6) # Always visible + self.toggle_strip.setStyleSheet("background-color: #90AF13;") + toggle_layout = QVBoxLayout(self.toggle_strip) + toggle_layout.setContentsMargins(0, 0, 0, 0) + toggle_layout.setSpacing(0) + toggle_layout.setAlignment(Qt.AlignVCenter | Qt.AlignRight) # Align to right for input dock + + self.toggle_btn = QPushButton("❯") # Right-pointing chevron for input dock + self.toggle_btn.setFixedSize(6, 60) + self.toggle_btn.setCursor(Qt.CursorShape.PointingHandCursor) + self.toggle_btn.clicked.connect(self.parent.input_dock_toggle) + self.toggle_btn.setToolTip("Show input panel") + self.toggle_btn.setObjectName("toggle_strip_button") + self.toggle_btn.setStyleSheet(""" + QPushButton { + background-color: #6c8408; + color: white; + font-size: 12px; + font-weight: bold; + padding: 0px; + border: none; + } + QPushButton:hover { + background-color: #5e7407; + } + """) + toggle_layout.addStretch() + toggle_layout.addWidget(self.toggle_btn) + toggle_layout.addStretch() + input_layout.addWidget(self.toggle_strip) + +class OutputDockIndicator(QWidget): + def __init__(self, parent): + super().__init__(parent) + # Ensures automatic deletion when closed + self.setAttribute(Qt.WA_DeleteOnClose, True) + self.parent = parent + self.setObjectName("output_dock_indicator") + self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Expanding) # Fixed width, expanding height + + output_layout = QHBoxLayout(self) + output_layout.setContentsMargins(0,0,0,0) + output_layout.setSpacing(0) + + self.toggle_strip = QWidget() + self.toggle_strip.setFixedWidth(6) # Always visible + self.toggle_strip.setObjectName("toggle_strip") + self.toggle_strip.setStyleSheet("background-color: #90AF13;") + toggle_layout = QVBoxLayout(self.toggle_strip) + toggle_layout.setContentsMargins(0, 0, 0, 0) + toggle_layout.setSpacing(0) + toggle_layout.setAlignment(Qt.AlignVCenter | Qt.AlignLeft) + + self.toggle_btn = QPushButton("❮") # Show state initially + self.toggle_btn.setCursor(Qt.CursorShape.PointingHandCursor) + self.toggle_btn.setFixedSize(6, 60) + self.toggle_btn.clicked.connect(self.parent.output_dock_toggle) + self.toggle_btn.setToolTip("Show panel") + self.toggle_btn.setObjectName("toggle_strip_button") + self.toggle_btn.setStyleSheet(""" + QPushButton { + background-color: #6c8408; + color: white; + font-size: 12px; + font-weight: bold; + padding: 0px; + border: none; + } + QPushButton:hover { + background-color: #5e7407; + } + """) + toggle_layout.addStretch() + toggle_layout.addWidget(self.toggle_btn) + toggle_layout.addStretch() + output_layout.addWidget(self.toggle_strip) + + self.output_label = QSvgWidget(":/vectors/outputs_label_light.svg") + output_layout.addWidget(self.output_label) + self.output_label.setFixedWidth(28) + From 0142877458be36533a80a4b8d6ac6af47454890c Mon Sep 17 00:00:00 2001 From: mhsuhail00 Date: Sat, 13 Dec 2025 05:06:17 +0530 Subject: [PATCH 48/59] Restyling additional inputs --- .../desktop/ui/dialogs/additional_inputs.py | 1928 +++++++++-------- .../desktop/ui/docks/input_dock.py | 36 +- src/osdagbridge/desktop/ui/docks/log_dock.py | 13 +- src/osdagbridge/desktop/ui/template_page.py | 4 +- 4 files changed, 986 insertions(+), 995 deletions(-) diff --git a/src/osdagbridge/desktop/ui/dialogs/additional_inputs.py b/src/osdagbridge/desktop/ui/dialogs/additional_inputs.py index 0c78ef78..cfa265ca 100644 --- a/src/osdagbridge/desktop/ui/dialogs/additional_inputs.py +++ b/src/osdagbridge/desktop/ui/dialogs/additional_inputs.py @@ -9,60 +9,76 @@ QComboBox, QGroupBox, QFormLayout, QPushButton, QScrollArea, QCheckBox, QMessageBox, QSizePolicy, QSpacerItem, QStackedWidget, QFrame, QGridLayout, QTableWidget, QTableWidgetItem, QHeaderView, - QTextEdit, QDialog + QTextEdit, QDialog, QSizePolicy, QSizeGrip ) from PySide6.QtCore import Qt, Signal, QSize from PySide6.QtGui import QDoubleValidator, QIntValidator from osdagbridge.core.utils.common import * +from osdagbridge.desktop.ui.utils.custom_titlebar import CustomTitleBar + +# ================================================================================= +# CENTRALIZED STYLING +# ================================================================================= def get_combobox_style(): """Return the common stylesheet for dropdowns with the SVG icon from resources.""" return """ - QComboBox { - padding: 2px 28px 2px 8px; - border: 1px solid #000000; + QComboBox{ + padding: 1px 7px; + border: 1px solid black; border-radius: 5px; - background-color: #ffffff; - color: #000000; - font-size: 12px; - min-height: 28px; - } - QComboBox:hover { - border: 1px solid #5d5d5d; - } - QComboBox:focus { - border: 1px solid #90AF13; + background-color: white; + color: black; } - QComboBox::drop-down { + QComboBox::drop-down{ subcontrol-origin: padding; subcontrol-position: top right; - width: 24px; - border: none; - margin-right: 4px; + border-left: 0px; } - QComboBox::down-arrow { + QComboBox::down-arrow{ image: url(:/vectors/arrow_down_light.svg); - width: 16px; - height: 16px; + width: 20px; + height: 20px; + margin-right: 8px; } QComboBox::down-arrow:on { image: url(:/vectors/arrow_up_light.svg); - width: 16px; - height: 16px; + width: 20px; + height: 20px; + margin-right: 8px; } - QComboBox QAbstractItemView { - background-color: #ffffff; - border: 1px solid #000000; + QComboBox QAbstractItemView{ + background-color: white; + border: 1px solid black; outline: none; } - QComboBox QAbstractItemView::item { - color: #000000; - padding: 4px 8px; + QComboBox QAbstractItemView::item{ + color: black; + background-color: white; + border: none; + border: 1px solid white; + border-radius: 0; + padding: 2px; } - QComboBox QAbstractItemView::item:selected { + QComboBox QAbstractItemView::item:hover{ + border: 1px solid #90AF13; background-color: #90AF13; - color: #000000; + color: black; + } + QComboBox QAbstractItemView::item:selected{ + background-color: #90AF13; + color: black; + border: 1px solid #90AF13; + } + QComboBox QAbstractItemView::item:selected:hover{ + background-color: #90AF13; + color: black; + border: 1px solid #94b816; + } + QComboBox:disabled{ + background: #f1f1f1; + color: #666; } """ @@ -74,21 +90,17 @@ def get_lineedit_style(): padding: 1px 7px; border: 1px solid #070707; border-radius: 6px; - background-color: #ffffff; + background-color: white; color: #000000; - font-size: 12px; - min-height: 28px; + font-weight: normal; + } + QLineEdit:disabled{ + background: #f1f1f1; + color: #666; } QLineEdit:hover { border: 1px solid #5d5d5d; } - QLineEdit:focus { - border: 1px solid #90AF13; - } - QLineEdit:disabled { - background-color: #f5f5f5; - color: #9b9b9b; - } """ @@ -106,28 +118,28 @@ def create_action_button_bar(parent=None): frame = QFrame(parent) frame.setObjectName("actionButtonBar") frame.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) - frame.setStyleSheet( - "QFrame#actionButtonBar {" - " background-color: #ededed;" - " border: 1px solid #c8c8c8;" - " border-radius: 6px;" - "}" - "QFrame#actionButtonBar QPushButton {" - " background-color: #ffffff;" - " color: #2f2f2f;" - " font-weight: 600;" - " border: 1px solid #8c8c8c;" - " border-radius: 4px;" - " padding: 6px 24px;" - " min-width: 120px;" - "}" - "QFrame#actionButtonBar QPushButton:hover {" - " background-color: #f6f6f6;" - "}" - "QFrame#actionButtonBar QPushButton:pressed {" - " background-color: #e0e0e0;" - "}" - ) + frame.setStyleSheet(""" + QFrame#actionButtonBar { + background-color: #ededed; + border: 1px solid #c8c8c8; + border-radius: 6px; + } + QFrame#actionButtonBar QPushButton { + background-color: #ffffff; + color: #2f2f2f; + font-weight: 600; + border: 1px solid #8c8c8c; + border-radius: 4px; + padding: 6px 24px; + min-width: 120px; + } + QFrame#actionButtonBar QPushButton:hover { + background-color: #f6f6f6; + } + QFrame#actionButtonBar QPushButton:pressed { + background-color: #e0e0e0; + } + """) layout = QHBoxLayout(frame) layout.setContentsMargins(22, 10, 22, 10) @@ -164,352 +176,665 @@ def create_action_button_bar(parent=None): } """ +# ================================================================================= +# MAIN IMPLEMENTATION +# ================================================================================= -class OptimizableField(QWidget): - """Widget that allows selection between Optimized/Customized/All modes with input field""" - - def __init__(self, label_text, parent=None): - super().__init__(parent) - self.layout = QHBoxLayout(self) - self.layout.setContentsMargins(0, 0, 0, 0) - self.layout.setSpacing(8) - - self.mode_combo = QComboBox() - self.mode_combo.addItems(VALUES_OPTIMIZATION_MODE) - self.mode_combo.setMinimumWidth(140) - self.mode_combo.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) - - self.input_field = QLineEdit() - self.input_field.setEnabled(False) - self.input_field.setVisible(False) - self.input_field.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) - - self.layout.addWidget(self.mode_combo) - self.layout.addWidget(self.input_field) - - self.mode_combo.currentTextChanged.connect(self.on_mode_changed) - self.on_mode_changed(self.mode_combo.currentText()) - - def on_mode_changed(self, text): - """Enable/disable input field based on selection""" - if text in ("Optimized", "All", "NA"): - self.input_field.setEnabled(False) - self.input_field.clear() - self.input_field.setVisible(False) - else: - self.input_field.setEnabled(True) - self.input_field.setVisible(True) - - def get_value(self): - """Returns tuple of (mode, value)""" - return (self.mode_combo.currentText(), self.input_field.text()) - - -class TypicalSectionDetailsTab(QWidget): - """Sub-tab for Typical Section Details inputs""" - - footpath_changed = Signal(str) - +class AdditionalInputs(QDialog): + """Main dialog for Additional Inputs with tabbed interface""" + def __init__(self, footpath_value="None", carriageway_width=7.5, parent=None): super().__init__(parent) + self.setObjectName("AdditionalInputs") + self.resize(1024, 720) + self.setMinimumSize(900, 520) + self.setSizeGripEnabled(True) self.footpath_value = footpath_value self.carriageway_width = carriageway_width - self.updating_fields = False self.init_ui() - - def style_input_field(self, field): - apply_field_style(field) - - def style_group_box(self, group_box): - group_box.setStyleSheet(""" - QGroupBox { - font-weight: bold; - border: 2px solid #d0d0d0; - border-radius: 6px; - margin-top: 12px; - padding-top: 15px; - background-color: #f9f9f9; - } - QGroupBox::title { - subcontrol-origin: margin; - subcontrol-position: top left; - left: 10px; - padding: 0 5px; - background-color: white; - color: #4a7ba7; - } - """) - - def _create_section_card(self, title): - card = QFrame() - card.setObjectName("sectionCard") - card.setStyleSheet(""" - QFrame#sectionCard { - background-color: #f5f5f5; - border: none; + self.setStyleSheet(""" + QDialog { + background-color: #ffffff; + border: 1px solid #90AF13; } """) - card_layout = QVBoxLayout(card) - card_layout.setContentsMargins(0, 0, 0, 0) - card_layout.setSpacing(12) - title_label = QLabel(title) - title_label.setStyleSheet("font-size: 12px; font-weight: bold; color: #000;") - card_layout.addWidget(title_label) - - return card, card_layout + def setupWrapper(self): + self.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowSystemMenuHint) - def init_ui(self): main_layout = QVBoxLayout(self) - main_layout.setContentsMargins(10, 10, 10, 10) + main_layout.setContentsMargins(1, 1, 1, 1) main_layout.setSpacing(0) + + self.title_bar = CustomTitleBar() + self.title_bar.setTitle("Additional Inputs") + main_layout.addWidget(self.title_bar) + + self.content_widget = QWidget(self) + main_layout.addWidget(self.content_widget, 1) - diagram_widget = QWidget() - diagram_widget.setStyleSheet(""" - QWidget { - background-color: #d9d9d9; - border: 1px solid #b0b0b0; - border-radius: 8px; - } - """) - diagram_widget.setMinimumHeight(150) - diagram_widget.setMaximumHeight(200) - diagram_layout = QVBoxLayout(diagram_widget) - diagram_layout.setContentsMargins(20, 20, 20, 20) - diagram_layout.setAlignment(Qt.AlignCenter) - - diagram_label = QLabel("Typical Section Details\nDiagram") - diagram_label.setAlignment(Qt.AlignCenter) - diagram_label.setStyleSheet(""" - QLabel { - background-color: transparent; - border: none; - padding: 20px; - font-size: 13px; - color: #333; - } - """) - diagram_layout.addWidget(diagram_label) - - main_layout.addWidget(diagram_widget) - main_layout.addSpacing(10) - - input_container = QWidget() - input_container.setStyleSheet("QWidget { background-color: white; }") - input_layout = QVBoxLayout(input_container) - input_layout.setContentsMargins(0, 0, 0, 0) - input_layout.setSpacing(0) + size_grip = QSizeGrip(self) + size_grip.setFixedSize(16, 16) - self.input_tabs = QTabWidget() - self.input_tabs.setStyleSheet(""" + overlay = QHBoxLayout() + overlay.setContentsMargins(0, 0, 4, 4) + overlay.addStretch(1) + overlay.addWidget(size_grip, 0, Qt.AlignBottom | Qt.AlignRight) + main_layout.addLayout(overlay) + + def init_ui(self): + self.setupWrapper() + + main_layout = QVBoxLayout(self.content_widget) + main_layout.setContentsMargins(5, 5, 5, 5) + + # Main tab widget + self.tabs = QTabWidget() + self.tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) + self.stretching_tab_bar = QTabBar() + self.stretching_tab_bar.setElideMode(Qt.ElideRight) + self.tabs.setTabBar(self.stretching_tab_bar) + self.tabs.setStyleSheet(""" QTabWidget::pane { - border: 1px solid #b0b0b0; - border-top: none; - background-color: #f5f5f5; - border-radius: 0px 0px 8px 8px; + border: 1px solid #d1d1d1; + background-color: #ffffff; + border-radius: 6px; } QTabBar::tab { - background-color: #e8e8e8; - color: #555; - padding: 10px 20px; - border: 1px solid #b0b0b0; - border-bottom: none; - border-right: none; - font-size: 11px; - min-width: 80px; - } - QTabBar::tab:last { - border-right: 1px solid #b0b0b0; + font-weight: bold; + font-size: 12px; + background: #ffffff; + color: #3a3a3a; + border: 1px solid #d1d1d1; + padding: 10px 22px; } QTabBar::tab:selected { - background-color: #90AF13; - color: white; - font-weight: bold; + background: #90AF13; + color: #ffffff; border: 1px solid #90AF13; - border-bottom: none; } - QTabBar::tab:hover:!selected { - background-color: #d0d0d0; + QTabBar::tab:hover { + background: #90AF13; + color: #ffffff; } """) - - self.create_layout_tab() - self.create_crash_barrier_tab() - self.create_median_tab() - self.create_railing_tab() - self.create_wearing_course_tab() - self.create_lane_details_tab() - - input_layout.addWidget(self.input_tabs) - main_layout.addWidget(input_container) + + # Sub-Tab 1: Typical Section Details + self.typical_section_tab = TypicalSectionDetailsTab(self.footpath_value, self.carriageway_width) + self.tabs.addTab(self.typical_section_tab, "Typical Section Details") + + # Sub-Tab 2: Member Properties + self.section_properties_tab = SectionPropertiesTab() + self.tabs.addTab(self.section_properties_tab, "Member Properties") + + # Sub-Tab 3: Loading + self.loading_tab = LoadingTab() + self.tabs.addTab(self.loading_tab, "Loading") + + # Sub-Tab 4: Support Conditions + support_tab = self._build_support_conditions_tab() + self.tabs.addTab(support_tab, "Support Conditions") + + # Sub-Tab 5: Design Options + design_options_tab = self._build_design_options_tab() + self.tabs.addTab(design_options_tab, "Design Options") + + # Sub-Tab 6: Design Options (Cont.) + analysis_design_tab = self.create_placeholder_tab( + "Design Options (Cont.)", + "This tab will contain:\n\n" + + "• Analysis Method\n" + + "• Design Code Options\n" + + "• Safety Factors\n" + + "• Other Design Parameters\n\n" + + "Implementation in progress..." + ) + self.tabs.addTab(analysis_design_tab, "Design Options (Cont.)") + + main_layout.addWidget(self.tabs) + action_bar, self.defaults_button, self.save_button = create_action_button_bar() self.defaults_button.clicked.connect(lambda: self._show_placeholder_message("Defaults")) self.save_button.clicked.connect(lambda: self._show_placeholder_message("Save")) - main_layout.addSpacing(8) + main_layout.addSpacing(6) main_layout.addWidget(action_bar) - self.deck_thickness.textChanged.connect(self.update_footpath_thickness) - self.recalculate_girders() - - def create_layout_tab(self): - layout_widget = QWidget() - layout_widget.setStyleSheet("background-color: #f5f5f5;") - layout_layout = QVBoxLayout(layout_widget) - layout_layout.setContentsMargins(18, 6, 18, 12) - layout_layout.setSpacing(0) - - title_label = QLabel("Inputs:") - title_label.setStyleSheet("font-size: 12px; font-weight: bold; color: #000;") - layout_layout.addWidget(title_label) - layout_layout.addSpacing(8) - - grid = QGridLayout() - grid.setHorizontalSpacing(24) - grid.setVerticalSpacing(10) - grid.setColumnStretch(1, 1) - grid.setColumnStretch(3, 1) - grid.setContentsMargins(0, 0, 0, 0) + def _show_placeholder_message(self, action_name): + """Show placeholder message for action buttons""" + QMessageBox.information(self, action_name, "This action will be available in an upcoming update.") - def _label(text): - lbl = QLabel(text) - lbl.setStyleSheet("font-size: 11px; color: #000;") - lbl.setMinimumWidth(180) - return lbl + def _build_support_conditions_tab(self): + """Build the Support Conditions tab matching reference design""" + widget = QWidget() + widget.setStyleSheet("background-color: #f5f5f5;") + + main_layout = QVBoxLayout(widget) + main_layout.setContentsMargins(12, 12, 12, 12) + main_layout.setSpacing(12) - self.girder_spacing = QLineEdit() - self.girder_spacing.setValidator(QDoubleValidator(0.01, 50.0, 3)) - self.girder_spacing.setText(str(DEFAULT_GIRDER_SPACING)) - self.style_input_field(self.girder_spacing) - self.girder_spacing.textChanged.connect(self.on_girder_spacing_changed) + # Main card + card = QFrame() + card.setStyleSheet("QFrame { border: 1px solid #b2b2b2; border-radius: 10px; background-color: #ffffff; }") + card_layout = QVBoxLayout(card) + card_layout.setContentsMargins(16, 16, 16, 16) + card_layout.setSpacing(16) - self.no_of_girders = QLineEdit() - self.no_of_girders.setValidator(QIntValidator(2, 100)) - self.style_input_field(self.no_of_girders) - self.no_of_girders.textChanged.connect(self.on_no_of_girders_changed) + label_style = "font-size: 11px; color: #3a3a3a; background: transparent; border: none;" + heading_style = "font-size: 12px; font-weight: 700; color: #2b2b2b; background: transparent; border: none;" + field_width = 120 - grid.addWidget(_label("Girder Spacing (m):"), 0, 0, Qt.AlignLeft) - grid.addWidget(self.girder_spacing, 0, 1) - grid.addWidget(_label("No. of Girders:"), 0, 2, Qt.AlignLeft) - grid.addWidget(self.no_of_girders, 0, 3) + # Support Condition section + support_title = QLabel("Support Condition*") + support_title.setStyleSheet(heading_style) + card_layout.addWidget(support_title) - self.deck_overhang = QLineEdit() - self.deck_overhang.setValidator(QDoubleValidator(0.0, 10.0, 3)) - self.deck_overhang.setText(str(DEFAULT_DECK_OVERHANG)) - self.style_input_field(self.deck_overhang) - self.deck_overhang.textChanged.connect(self.on_deck_overhang_changed) + support_grid = QGridLayout() + support_grid.setContentsMargins(0, 8, 0, 0) + support_grid.setHorizontalSpacing(12) + support_grid.setVerticalSpacing(12) + support_grid.setColumnMinimumWidth(0, 120) - values_adjusted_label = QLabel("Values adjusted for:") - values_adjusted_label.setStyleSheet("font-size: 11px; color: #5b5b5b; font-style: italic;") + # Left Support + lbl = QLabel("Left Support:") + lbl.setStyleSheet(label_style) + self.left_support_combo = QComboBox() + self.left_support_combo.addItems(["Fixed", "Pinned", "Roller"]) + self.left_support_combo.setFixedWidth(field_width) + apply_field_style(self.left_support_combo) + support_grid.addWidget(lbl, 0, 0, Qt.AlignLeft | Qt.AlignVCenter) + support_grid.addWidget(self.left_support_combo, 0, 1, Qt.AlignLeft) - grid.addWidget(_label("Deck Overhang Width (m):"), 1, 0, Qt.AlignLeft) - grid.addWidget(self.deck_overhang, 1, 1) - #grid.addWidget(values_adjusted_label, 1, 2, 1, 2, Qt.AlignLeft) + # Right Support + lbl = QLabel("Right Support:") + lbl.setStyleSheet(label_style) + self.right_support_combo = QComboBox() + self.right_support_combo.addItems(["Fixed", "Pinned", "Roller"]) + self.right_support_combo.setFixedWidth(field_width) + apply_field_style(self.right_support_combo) + support_grid.addWidget(lbl, 1, 0, Qt.AlignLeft | Qt.AlignVCenter) + support_grid.addWidget(self.right_support_combo, 1, 1, Qt.AlignLeft) - self.overall_bridge_width_display = QLineEdit() - self.style_input_field(self.overall_bridge_width_display) - self.overall_bridge_width_display.setReadOnly(True) - self.overall_bridge_width_display.setEnabled(False) + card_layout.addLayout(support_grid) - grid.addWidget(_label("Overall Bridge Width (m):"), 2, 0, Qt.AlignLeft) - grid.addWidget(self.overall_bridge_width_display, 2, 1) + # Bearing Length section + bearing_title = QLabel("Bearing length*") + bearing_title.setStyleSheet(heading_style) + card_layout.addWidget(bearing_title) - self.deck_thickness = QLineEdit() - self.deck_thickness.setValidator(QDoubleValidator(0.0, 500.0, 0)) - self.style_input_field(self.deck_thickness) + bearing_grid = QGridLayout() + bearing_grid.setContentsMargins(0, 8, 0, 0) + bearing_grid.setHorizontalSpacing(12) + bearing_grid.setVerticalSpacing(12) + bearing_grid.setColumnMinimumWidth(0, 120) - self.footpath_thickness = QLineEdit() - self.footpath_thickness.setValidator(QDoubleValidator(0.0, 500.0, 0)) - self.style_input_field(self.footpath_thickness) + lbl = QLabel("Bearing Length Value") + lbl.setStyleSheet(label_style) + self.bearing_length_input = QLineEdit() + self.bearing_length_input.setText("0") + self.bearing_length_input.setFixedWidth(field_width) + apply_field_style(self.bearing_length_input) + bearing_grid.addWidget(lbl, 0, 0, Qt.AlignLeft | Qt.AlignVCenter) + bearing_grid.addWidget(self.bearing_length_input, 0, 1, Qt.AlignLeft) - grid.addWidget(_label("Deck Thickness (mm):"), 3, 0, Qt.AlignLeft) - grid.addWidget(self.deck_thickness, 3, 1) - grid.addWidget(_label("Footpath Thickness (mm):"), 4, 2, Qt.AlignLeft) - grid.addWidget(self.footpath_thickness, 4, 3) + card_layout.addLayout(bearing_grid) + card_layout.addStretch() - self.footpath_width = QLineEdit() - self.footpath_width.setValidator(QDoubleValidator(MIN_FOOTPATH_WIDTH, 5.0, 3)) - self.footpath_width.textChanged.connect(self.on_footpath_width_changed) - self.style_input_field(self.footpath_width) - self.footpath_width.setText(f"{MIN_FOOTPATH_WIDTH:.2f}") + main_layout.addWidget(card) + main_layout.addStretch() - grid.addWidget(_label("Footpath Width (m):"), 4, 0, Qt.AlignLeft) - grid.addWidget(self.footpath_width, 4, 1) + return widget - layout_layout.addLayout(grid) - # CHANGED: Add stretch at bottom to push content up - layout_layout.addStretch() + def _build_design_options_tab(self): + """Build the Design Options tab matching reference design""" + widget = QWidget() + widget.setStyleSheet("background-color: #f5f5f5;") - self.input_tabs.addTab(layout_widget, "Layout") - def create_crash_barrier_tab(self): - crash_widget = QWidget() - crash_widget.setStyleSheet("background-color: #f5f5f5;") - crash_layout = QVBoxLayout(crash_widget) - crash_layout.setContentsMargins(18, 6, 18, 12) - crash_layout.setSpacing(0) - - card, card_layout = self._create_section_card("Crash Barrier Inputs:") - grid = QGridLayout() - grid.setContentsMargins(0, 0, 0, 0) - grid.setHorizontalSpacing(24) - grid.setVerticalSpacing(10) - grid.setColumnStretch(1, 1) + main_layout = QVBoxLayout(widget) + main_layout.setContentsMargins(12, 12, 12, 12) + main_layout.setSpacing(12) - def add_row(row, label_text, widget): - label = QLabel(label_text) - label.setStyleSheet("font-size: 11px; color: #000;") - label.setMinimumWidth(210) - grid.addWidget(label, row, 0, Qt.AlignLeft) - grid.addWidget(widget, row, 1) + # Main card + card = QFrame() + card.setStyleSheet("QFrame { border: 1px solid #b2b2b2; border-radius: 10px; background-color: #ffffff; }") + card_layout = QVBoxLayout(card) + card_layout.setContentsMargins(16, 16, 16, 16) + card_layout.setSpacing(12) - self.crash_barrier_type = QComboBox() - self.crash_barrier_type.addItems(VALUES_CRASH_BARRIER_TYPE) - self.style_input_field(self.crash_barrier_type) - self.crash_barrier_type.currentTextChanged.connect(self.on_crash_barrier_type_changed) - add_row(0, "Type:", self.crash_barrier_type) + label_style = "font-size: 11px; color: #3a3a3a; background: transparent; border: none;" + heading_style = "font-size: 12px; font-weight: 700; color: #2b2b2b; background: transparent; border: none;" + field_width = 120 - self.crash_barrier_density = QLineEdit() - self.crash_barrier_density.setValidator(QDoubleValidator(0.0, 100.0, 2)) - self.style_input_field(self.crash_barrier_density) - add_row(1, "Material Density (kN/m^3):", self.crash_barrier_density) + # Deck Design section + deck_title = QLabel("Deck Design:") + deck_title.setStyleSheet(heading_style) + card_layout.addWidget(deck_title) - self.crash_barrier_width = QLineEdit() - self.crash_barrier_width.setValidator(QDoubleValidator(0.0, 2.0, 3)) - self.crash_barrier_width.setText(str(DEFAULT_CRASH_BARRIER_WIDTH)) - self.style_input_field(self.crash_barrier_width) - self.crash_barrier_width.textChanged.connect(self.recalculate_girders) - add_row(2, "Width (m):", self.crash_barrier_width) + deck_grid = QGridLayout() + deck_grid.setContentsMargins(0, 4, 0, 0) + deck_grid.setHorizontalSpacing(12) + deck_grid.setVerticalSpacing(10) + deck_grid.setColumnMinimumWidth(0, 120) - self.crash_barrier_height = QLineEdit() - self.crash_barrier_height.setValidator(QDoubleValidator(0.0, 3.0, 3)) - self.style_input_field(self.crash_barrier_height) - add_row(3, "Height (m):", self.crash_barrier_height) + lbl = QLabel("Reinforcement Size:") + lbl.setStyleSheet(label_style) + self.reinforcement_size_combo = QComboBox() + self.reinforcement_size_combo.addItems(["8 mm", "10 mm", "12 mm", "16 mm", "20 mm"]) + self.reinforcement_size_combo.setFixedWidth(field_width) + apply_field_style(self.reinforcement_size_combo) + deck_grid.addWidget(lbl, 0, 0, Qt.AlignLeft | Qt.AlignVCenter) + deck_grid.addWidget(self.reinforcement_size_combo, 0, 1, Qt.AlignLeft) - self.crash_barrier_area = QLineEdit() - self.crash_barrier_area.setValidator(QDoubleValidator(0.0, 10.0, 4)) - self.style_input_field(self.crash_barrier_area) - add_row(4, "Area (m^2):", self.crash_barrier_area) + card_layout.addLayout(deck_grid) - card_layout.addLayout(grid) - crash_layout.addWidget(card) - crash_layout.addStretch() - self.input_tabs.addTab(crash_widget, "Crash Barrier") + # Shear Studs section + shear_title = QLabel("Shear Studs:") + shear_title.setStyleSheet(heading_style) + card_layout.addWidget(shear_title) - def create_median_tab(self): - median_widget = QWidget() - median_widget.setStyleSheet("background-color: #f5f5f5;") - median_layout = QVBoxLayout(median_widget) - median_layout.setContentsMargins(18, 6, 18, 12) - median_layout.setSpacing(0) + shear_grid = QGridLayout() + shear_grid.setContentsMargins(0, 4, 0, 0) + shear_grid.setHorizontalSpacing(12) + shear_grid.setVerticalSpacing(10) + shear_grid.setColumnMinimumWidth(0, 120) - card, card_layout = self._create_section_card("Median Inputs:") - grid = QGridLayout() - grid.setContentsMargins(0, 0, 0, 0) - grid.setHorizontalSpacing(24) - grid.setVerticalSpacing(10) - grid.setColumnStretch(1, 1) + # Material + lbl = QLabel("Material:") + lbl.setStyleSheet(label_style) + self.shear_stud_material_input = QLineEdit() + self.shear_stud_material_input.setFixedWidth(field_width) + apply_field_style(self.shear_stud_material_input) + shear_grid.addWidget(lbl, 0, 0, Qt.AlignLeft | Qt.AlignVCenter) + shear_grid.addWidget(self.shear_stud_material_input, 0, 1, Qt.AlignLeft) - def add_row(row, label_text, widget): + # Diameter + lbl = QLabel("Diameter (mm):") + lbl.setStyleSheet(label_style) + self.shear_stud_diameter_input = QLineEdit() + self.shear_stud_diameter_input.setFixedWidth(field_width) + apply_field_style(self.shear_stud_diameter_input) + shear_grid.addWidget(lbl, 1, 0, Qt.AlignLeft | Qt.AlignVCenter) + shear_grid.addWidget(self.shear_stud_diameter_input, 1, 1, Qt.AlignLeft) + + # Height + lbl = QLabel("Height (mm):") + lbl.setStyleSheet(label_style) + self.shear_stud_height_input = QLineEdit() + self.shear_stud_height_input.setFixedWidth(field_width) + apply_field_style(self.shear_stud_height_input) + shear_grid.addWidget(lbl, 2, 0, Qt.AlignLeft | Qt.AlignVCenter) + shear_grid.addWidget(self.shear_stud_height_input, 2, 1, Qt.AlignLeft) + + card_layout.addLayout(shear_grid) + card_layout.addStretch() + + main_layout.addWidget(card) + main_layout.addStretch() + + return widget + + def create_placeholder_tab(self, title, description): + """Create a styled placeholder tab with title and description""" + widget = QWidget() + widget.setStyleSheet("background-color: white;") + + layout = QVBoxLayout(widget) + layout.setAlignment(Qt.AlignCenter) + layout.setContentsMargins(40, 40, 40, 40) + + # Icon or visual indicator + icon_label = QLabel("🚧") + icon_label.setStyleSheet("font-size: 48px;") + icon_label.setAlignment(Qt.AlignCenter) + layout.addWidget(icon_label) + + # Title + title_label = QLabel(title) + title_label.setStyleSheet(""" + font-size: 18px; + font-weight: bold; + color: #333; + margin-top: 20px; + margin-bottom: 10px; + """) + title_label.setAlignment(Qt.AlignCenter) + layout.addWidget(title_label) + + # Status + status_label = QLabel("Under Development") + status_label.setStyleSheet(""" + font-size: 14px; + color: #f39c12; + font-weight: bold; + margin-bottom: 20px; + """) + status_label.setAlignment(Qt.AlignCenter) + layout.addWidget(status_label) + + # Description + desc_label = QLabel(description) + desc_label.setStyleSheet(""" + font-size: 12px; + color: #666; + line-height: 1.6; + """) + desc_label.setAlignment(Qt.AlignCenter) + desc_label.setWordWrap(True) + desc_label.setMaximumWidth(600) + layout.addWidget(desc_label) + + layout.addStretch() + + return widget + + def update_footpath_value(self, footpath_value): + """Update footpath value across all tabs""" + self.footpath_value = footpath_value + self.typical_section_tab.update_footpath_value(footpath_value) + +# ================================================================================= +# SUB COMPONENTS +# ================================================================================= + +class TypicalSectionDetailsTab(QWidget): + """Sub-tab for Typical Section Details inputs""" + + footpath_changed = Signal(str) + + def __init__(self, footpath_value="None", carriageway_width=7.5, parent=None): + super().__init__(parent) + self.footpath_value = footpath_value + self.carriageway_width = carriageway_width + self.updating_fields = False + self.init_ui() + + def style_input_field(self, field): + apply_field_style(field) + + def style_group_box(self, group_box): + group_box.setStyleSheet(""" + QGroupBox { + font-weight: bold; + border: 2px solid #d0d0d0; + border-radius: 6px; + margin-top: 12px; + padding-top: 15px; + background-color: #f9f9f9; + } + QGroupBox::title { + subcontrol-origin: margin; + subcontrol-position: top left; + left: 10px; + padding: 0 5px; + background-color: white; + color: #4a7ba7; + } + """) + + def _create_section_card(self, title): + card = QFrame() + card.setObjectName("sectionCard") + card.setStyleSheet(""" + QFrame#sectionCard { + background-color: #f5f5f5; + border: none; + } + """) + card_layout = QVBoxLayout(card) + card_layout.setContentsMargins(0, 0, 0, 0) + card_layout.setSpacing(12) + + title_label = QLabel(title) + title_label.setStyleSheet("font-size: 12px; font-weight: bold; color: #000;") + card_layout.addWidget(title_label) + + return card, card_layout + + def init_ui(self): + main_layout = QVBoxLayout(self) + main_layout.setContentsMargins(10, 10, 10, 10) + main_layout.setSpacing(0) + + diagram_widget = QWidget() + diagram_widget.setStyleSheet(""" + QWidget { + background: transparent; + border: 1px solid #b0b0b0; + border-radius: 8px; + } + """) + diagram_widget.setMinimumHeight(150) + diagram_widget.setMaximumHeight(200) + diagram_layout = QVBoxLayout(diagram_widget) + diagram_layout.setContentsMargins(20, 20, 20, 20) + diagram_layout.setAlignment(Qt.AlignCenter) + + diagram_label = QLabel("Typical Section Details\nDiagram") + diagram_label.setAlignment(Qt.AlignCenter) + diagram_label.setStyleSheet(""" + QLabel { + background-color: transparent; + border: none; + padding: 20px; + font-size: 13px; + color: #333; + } + """) + diagram_layout.addWidget(diagram_label) + + main_layout.addWidget(diagram_widget) + main_layout.addSpacing(10) + + input_container = QWidget() + input_container.setStyleSheet("QWidget { background-color: white; }") + input_layout = QVBoxLayout(input_container) + input_layout.setContentsMargins(0, 0, 0, 0) + input_layout.setSpacing(0) + + self.input_tabs = QTabWidget() + self.input_tabs.setStyleSheet(""" + QTabWidget::pane { + border: 1px solid #b0b0b0; + border-top: none; + background-color: #f5f5f5; + border-radius: 0px 0px 8px 8px; + } + QTabBar::tab { + background-color: #e8e8e8; + color: #555; + padding: 10px 20px; + border: 1px solid #b0b0b0; + border-bottom: none; + border-right: none; + font-size: 11px; + min-width: 80px; + } + QTabBar::tab:last { + border-right: 1px solid #b0b0b0; + } + QTabBar::tab:selected { + background-color: #90AF13; + color: white; + font-weight: bold; + border: 1px solid #90AF13; + border-bottom: none; + } + QTabBar::tab:hover:!selected { + background-color: #d0d0d0; + } + """) + + self.create_layout_tab() + self.create_crash_barrier_tab() + self.create_median_tab() + self.create_railing_tab() + self.create_wearing_course_tab() + self.create_lane_details_tab() + + input_layout.addWidget(self.input_tabs) + main_layout.addWidget(input_container) + + self.deck_thickness.textChanged.connect(self.update_footpath_thickness) + self.recalculate_girders() + + def create_layout_tab(self): + layout_widget = QWidget() + layout_widget.setStyleSheet("background-color: #f5f5f5;") + layout_layout = QVBoxLayout(layout_widget) + layout_layout.setContentsMargins(18, 6, 18, 12) + layout_layout.setSpacing(0) + + title_label = QLabel("Inputs:") + title_label.setStyleSheet("font-size: 12px; font-weight: bold; color: #000;") + layout_layout.addWidget(title_label) + layout_layout.addSpacing(8) + + grid = QGridLayout() + grid.setHorizontalSpacing(24) + grid.setVerticalSpacing(10) + grid.setColumnStretch(1, 1) + grid.setColumnStretch(3, 1) + grid.setContentsMargins(0, 0, 0, 0) + + def _label(text): + lbl = QLabel(text) + lbl.setStyleSheet("font-size: 11px; color: #000;") + lbl.setMinimumWidth(180) + return lbl + + self.girder_spacing = QLineEdit() + self.girder_spacing.setValidator(QDoubleValidator(0.01, 50.0, 3)) + self.girder_spacing.setText(str(DEFAULT_GIRDER_SPACING)) + self.style_input_field(self.girder_spacing) + self.girder_spacing.textChanged.connect(self.on_girder_spacing_changed) + + self.no_of_girders = QLineEdit() + self.no_of_girders.setValidator(QIntValidator(2, 100)) + self.style_input_field(self.no_of_girders) + self.no_of_girders.textChanged.connect(self.on_no_of_girders_changed) + + grid.addWidget(_label("Girder Spacing (m):"), 0, 0, Qt.AlignLeft) + grid.addWidget(self.girder_spacing, 0, 1) + grid.addWidget(_label("No. of Girders:"), 0, 2, Qt.AlignLeft) + grid.addWidget(self.no_of_girders, 0, 3) + + self.deck_overhang = QLineEdit() + self.deck_overhang.setValidator(QDoubleValidator(0.0, 10.0, 3)) + self.deck_overhang.setText(str(DEFAULT_DECK_OVERHANG)) + self.style_input_field(self.deck_overhang) + self.deck_overhang.textChanged.connect(self.on_deck_overhang_changed) + + values_adjusted_label = QLabel("Values adjusted for:") + values_adjusted_label.setStyleSheet("font-size: 11px; color: #5b5b5b; font-style: italic;") + + grid.addWidget(_label("Deck Overhang Width (m):"), 1, 0, Qt.AlignLeft) + grid.addWidget(self.deck_overhang, 1, 1) + #grid.addWidget(values_adjusted_label, 1, 2, 1, 2, Qt.AlignLeft) + + self.overall_bridge_width_display = QLineEdit() + self.style_input_field(self.overall_bridge_width_display) + self.overall_bridge_width_display.setReadOnly(True) + self.overall_bridge_width_display.setEnabled(False) + + grid.addWidget(_label("Overall Bridge Width (m):"), 2, 0, Qt.AlignLeft) + grid.addWidget(self.overall_bridge_width_display, 2, 1) + + self.deck_thickness = QLineEdit() + self.deck_thickness.setValidator(QDoubleValidator(0.0, 500.0, 0)) + self.style_input_field(self.deck_thickness) + + self.footpath_thickness = QLineEdit() + self.footpath_thickness.setValidator(QDoubleValidator(0.0, 500.0, 0)) + self.style_input_field(self.footpath_thickness) + + grid.addWidget(_label("Deck Thickness (mm):"), 3, 0, Qt.AlignLeft) + grid.addWidget(self.deck_thickness, 3, 1) + grid.addWidget(_label("Footpath Thickness (mm):"), 4, 2, Qt.AlignLeft) + grid.addWidget(self.footpath_thickness, 4, 3) + + self.footpath_width = QLineEdit() + self.footpath_width.setValidator(QDoubleValidator(MIN_FOOTPATH_WIDTH, 5.0, 3)) + self.footpath_width.textChanged.connect(self.on_footpath_width_changed) + self.style_input_field(self.footpath_width) + self.footpath_width.setText(f"{MIN_FOOTPATH_WIDTH:.2f}") + + grid.addWidget(_label("Footpath Width (m):"), 4, 0, Qt.AlignLeft) + grid.addWidget(self.footpath_width, 4, 1) + + layout_layout.addLayout(grid) + # CHANGED: Add stretch at bottom to push content up + layout_layout.addStretch() + + self.input_tabs.addTab(layout_widget, "Layout") + def create_crash_barrier_tab(self): + crash_widget = QWidget() + crash_widget.setStyleSheet("background-color: #f5f5f5;") + crash_layout = QVBoxLayout(crash_widget) + crash_layout.setContentsMargins(18, 6, 18, 12) + crash_layout.setSpacing(0) + + card, card_layout = self._create_section_card("Crash Barrier Inputs:") + grid = QGridLayout() + grid.setContentsMargins(0, 0, 0, 0) + grid.setHorizontalSpacing(24) + grid.setVerticalSpacing(10) + grid.setColumnStretch(1, 1) + + def add_row(row, label_text, widget): + label = QLabel(label_text) + label.setStyleSheet("font-size: 11px; color: #000;") + label.setMinimumWidth(210) + grid.addWidget(label, row, 0, Qt.AlignLeft) + grid.addWidget(widget, row, 1) + + self.crash_barrier_type = QComboBox() + self.crash_barrier_type.addItems(VALUES_CRASH_BARRIER_TYPE) + self.style_input_field(self.crash_barrier_type) + self.crash_barrier_type.currentTextChanged.connect(self.on_crash_barrier_type_changed) + add_row(0, "Type:", self.crash_barrier_type) + + self.crash_barrier_density = QLineEdit() + self.crash_barrier_density.setValidator(QDoubleValidator(0.0, 100.0, 2)) + self.style_input_field(self.crash_barrier_density) + add_row(1, "Material Density (kN/m^3):", self.crash_barrier_density) + + self.crash_barrier_width = QLineEdit() + self.crash_barrier_width.setValidator(QDoubleValidator(0.0, 2.0, 3)) + self.crash_barrier_width.setText(str(DEFAULT_CRASH_BARRIER_WIDTH)) + self.style_input_field(self.crash_barrier_width) + self.crash_barrier_width.textChanged.connect(self.recalculate_girders) + add_row(2, "Width (m):", self.crash_barrier_width) + + self.crash_barrier_height = QLineEdit() + self.crash_barrier_height.setValidator(QDoubleValidator(0.0, 3.0, 3)) + self.style_input_field(self.crash_barrier_height) + add_row(3, "Height (m):", self.crash_barrier_height) + + self.crash_barrier_area = QLineEdit() + self.crash_barrier_area.setValidator(QDoubleValidator(0.0, 10.0, 4)) + self.style_input_field(self.crash_barrier_area) + add_row(4, "Area (m^2):", self.crash_barrier_area) + + card_layout.addLayout(grid) + crash_layout.addWidget(card) + crash_layout.addStretch() + self.input_tabs.addTab(crash_widget, "Crash Barrier") + + def create_median_tab(self): + median_widget = QWidget() + median_widget.setStyleSheet("background-color: #f5f5f5;") + median_layout = QVBoxLayout(median_widget) + median_layout.setContentsMargins(18, 6, 18, 12) + median_layout.setSpacing(0) + + card, card_layout = self._create_section_card("Median Inputs:") + grid = QGridLayout() + grid.setContentsMargins(0, 0, 0, 0) + grid.setHorizontalSpacing(24) + grid.setVerticalSpacing(10) + grid.setColumnStretch(1, 1) + + def add_row(row, label_text, widget): label = QLabel(label_text) label.setStyleSheet("font-size: 11px; color: #000;") label.setMinimumWidth(210) @@ -914,8 +1239,47 @@ def _update_lane_details_rows(self, count): if existing_item is None: self.lane_table.setItem(row, col, QTableWidgetItem("")) - def _show_placeholder_message(self, action_name): - QMessageBox.information(self, action_name, "This action will be available in an upcoming update.") + def _show_placeholder_message(self, action_name): + QMessageBox.information(self, action_name, "This action will be available in an upcoming update.") + +class OptimizableField(QWidget): + """Widget that allows selection between Optimized/Customized/All modes with input field""" + + def __init__(self, label_text, parent=None): + super().__init__(parent) + self.layout = QHBoxLayout(self) + self.layout.setContentsMargins(0, 0, 0, 0) + self.layout.setSpacing(8) + + self.mode_combo = QComboBox() + self.mode_combo.addItems(VALUES_OPTIMIZATION_MODE) + self.mode_combo.setMinimumWidth(140) + self.mode_combo.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) + + self.input_field = QLineEdit() + self.input_field.setEnabled(False) + self.input_field.setVisible(False) + self.input_field.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) + + self.layout.addWidget(self.mode_combo) + self.layout.addWidget(self.input_field) + + self.mode_combo.currentTextChanged.connect(self.on_mode_changed) + self.on_mode_changed(self.mode_combo.currentText()) + + def on_mode_changed(self, text): + """Enable/disable input field based on selection""" + if text in ("Optimized", "All", "NA"): + self.input_field.setEnabled(False) + self.input_field.clear() + self.input_field.setVisible(False) + else: + self.input_field.setEnabled(True) + self.input_field.setVisible(True) + + def get_value(self): + """Returns tuple of (mode, value)""" + return (self.mode_combo.currentText(), self.input_field.text()) class SectionPropertiesTab(QWidget): """Sub-tab for Section Properties with custom navigation layout.""" @@ -936,8 +1300,8 @@ def init_ui(self): nav_bar = QWidget() nav_bar.setStyleSheet("background-color: white;") nav_bar_layout = QHBoxLayout(nav_bar) - nav_bar_layout.setContentsMargins(0, 0, 0, 0) - nav_bar_layout.setSpacing(0) + nav_bar_layout.setContentsMargins(6, 0, 6, 0) + nav_bar_layout.setSpacing(5) main_layout.addWidget(nav_bar) @@ -978,7 +1342,7 @@ def init_ui(self): color: #333; border: 1px solid #b0b0b0; border-right: none; - padding: 6px 14px; + padding: 3px 10px; text-align: center; font-size: 10px; font-weight: normal; @@ -1014,17 +1378,12 @@ def init_ui(self): self.nav_buttons[0].setChecked(True) self.stack.setCurrentIndex(0) - action_bar, self.defaults_button, self.save_button = create_action_button_bar() - main_layout.addSpacing(6) - main_layout.addWidget(action_bar) - def switch_section(self, index): """Switch the stacked widget page and update navigation states.""" self.stack.setCurrentIndex(index) for btn_index, button in enumerate(self.nav_buttons): button.setChecked(btn_index == index) - class GirderDetailsTab(QWidget): """Tab for Girder Details styled to match the provided reference.""" @@ -1362,48 +1721,48 @@ def _on_type_changed(self, text): def _create_inner_box(self): """Create a bordered box for grouped controls""" box = QFrame() - box.setStyleSheet( - "QFrame {" - " border: 1px solid #b0b0b0;" - " border-radius: 6px;" - " background-color: #ffffff;" - "}" - "QFrame QComboBox, QFrame QLineEdit {" - " border: none;" - " border-bottom: 1px solid #d0d0d0;" - " border-radius: 0px;" - " min-height: 28px;" - " padding: 4px 8px;" - " background-color: #ffffff;" - "}" - "QFrame QComboBox:hover, QFrame QLineEdit:hover {" - " border-bottom: 1px solid #5d5d5d;" - "}" - "QFrame QComboBox:focus, QFrame QLineEdit:focus {" - " border-bottom: 1px solid #90AF13;" - "}" - "QFrame QLabel {" - " border: none;" - " padding: 0px;" - " margin: 0px;" - "}" - ) + box.setStyleSheet(""" + QFrame { + border: 1px solid #b0b0b0; + border-radius: 6px; + background-color: #ffffff; + } + QFrame QComboBox, QFrame QLineEdit { + border: none; + border-bottom: 1px solid #d0d0d0; + border-radius: 0px; + min-height: 28px; + padding: 4px 8px; + background-color: #ffffff; + } + QFrame QComboBox:hover, QFrame QLineEdit:hover { + border-bottom: 1px solid #5d5d5d; + } + QFrame QComboBox:focus, QFrame QLineEdit:focus { + border-bottom: 1px solid #90AF13; + } + QFrame QLabel { + border: none; + padding: 0px; + margin: 0px; + } + """) return box def _create_small_label(self, text): """Create a smaller label for compact layouts""" label = QLabel(text) - label.setStyleSheet( - "QLabel {" - " color: #2b2b2b;" - " font-size: 11px;" - " font-weight: 500;" - " background: transparent;" - " border: none;" - " padding: 0px;" - " margin: 0px;" - "}" - ) + label.setStyleSheet(""" + QLabel { + color: #2b2b2b; + font-size: 11px; + font-weight: 500; + background: transparent; + border: none; + padding: 0px; + margin: 0px; + } + """) label.setAutoFillBackground(False) return label @@ -1421,7 +1780,6 @@ def _set_row_visibility(self, rows, visible): label.setVisible(visible) widget.setVisible(visible) - class StiffenerDetailsTab(QWidget): """Tab for Stiffener Details with compact layout""" @@ -1837,7 +2195,6 @@ def _update_previews(self): self.top_bracket_preview_label.setText(self.top_bracket_size_combo.currentText()) self.bottom_bracket_preview_label.setText(self.bottom_bracket_size_combo.currentText()) - class EndDiaphragmDetailsTab(QWidget): """Tab for End Diaphragm Details with type-specific layouts""" @@ -2302,7 +2659,6 @@ def _set_current_type(self, target): selector.setCurrentText(target) self.block_type_sync = False - class CustomVehicleDialog(QDialog): """Dialog for adding or editing custom live load vehicles""" @@ -2313,7 +2669,7 @@ def __init__(self, parent=None): self.setMinimumWidth(420) self.setMinimumHeight(500) self.setStyleSheet(""" - QDialog { background-color: #f5f5f5; } + QDialog { background-color: #ffffff; } QLabel { color: #2b2b2b; font-size: 11px; background: transparent; } QLineEdit { background-color: #ffffff; @@ -2477,7 +2833,6 @@ def init_ui(self): layout.addStretch() - class LoadingTab(QWidget): """Loading tab with permanent load layout and load-type subtabs""" @@ -2511,10 +2866,6 @@ def init_ui(self): self.load_tabs.addTab(self._build_load_combination_tab(), "Load Combination") main_layout.addWidget(self.load_tabs) - action_bar, self.defaults_button, self.save_button = create_action_button_bar() - main_layout.addSpacing(6) - main_layout.addWidget(action_bar) - def _build_permanent_load_tab(self): page = QWidget() page.setStyleSheet("background-color: #f5f5f5;") @@ -3423,594 +3774,261 @@ def _build_temperature_load_tab(self): lbl = QLabel("Lowest Minimum Air Temperature:") lbl.setStyleSheet(label_style) self.lowest_min_temp_input = QLineEdit() - self.lowest_min_temp_input.setFixedWidth(field_width) - apply_field_style(self.lowest_min_temp_input) - irc6_grid.addWidget(lbl, 1, 0, Qt.AlignLeft | Qt.AlignVCenter) - irc6_grid.addWidget(self.lowest_min_temp_input, 1, 1, Qt.AlignLeft) - - irc6_layout.addLayout(irc6_grid) - left_layout.addWidget(irc6_box) - - # ===== Range of Effective Bridge Temperature Box ===== - range_box = QFrame() - range_box.setStyleSheet("QFrame { border: 1px solid #b2b2b2; border-radius: 8px; background-color: #ffffff; }") - range_layout = QVBoxLayout(range_box) - range_layout.setContentsMargins(12, 12, 12, 12) - range_layout.setSpacing(10) - - range_title = QLabel("Range of Effective Bridge Temperature:") - range_title.setStyleSheet("font-size: 12px; font-weight: 700; color: #2b2b2b; background: transparent; border: none;") - range_layout.addWidget(range_title) - - range_grid = QGridLayout() - range_grid.setContentsMargins(0, 4, 0, 0) - range_grid.setHorizontalSpacing(12) - range_grid.setVerticalSpacing(10) - range_grid.setColumnMinimumWidth(0, 200) - - # Minimum - lbl = QLabel("Minimum:") - lbl.setStyleSheet(label_style) - self.bridge_temp_min_input = QLineEdit() - self.bridge_temp_min_input.setFixedWidth(field_width) - apply_field_style(self.bridge_temp_min_input) - range_grid.addWidget(lbl, 0, 0, Qt.AlignLeft | Qt.AlignVCenter) - range_grid.addWidget(self.bridge_temp_min_input, 0, 1, Qt.AlignLeft) - - # Maximum - lbl = QLabel("Maximum:") - lbl.setStyleSheet(label_style) - self.bridge_temp_max_input = QLineEdit() - self.bridge_temp_max_input.setFixedWidth(field_width) - apply_field_style(self.bridge_temp_max_input) - range_grid.addWidget(lbl, 1, 0, Qt.AlignLeft | Qt.AlignVCenter) - range_grid.addWidget(self.bridge_temp_max_input, 1, 1, Qt.AlignLeft) - - range_layout.addLayout(range_grid) - left_layout.addWidget(range_box) - - # ===== Coefficient of Thermal Expansion Box ===== - coeff_box = QFrame() - coeff_box.setStyleSheet("QFrame { border: 1px solid #b2b2b2; border-radius: 8px; background-color: #ffffff; }") - coeff_layout = QGridLayout(coeff_box) - coeff_layout.setContentsMargins(12, 12, 12, 12) - coeff_layout.setHorizontalSpacing(12) - coeff_layout.setVerticalSpacing(10) - coeff_layout.setColumnMinimumWidth(0, 200) - - lbl = QLabel("Coefficient of Thermal Expansion for Steel:") - lbl.setStyleSheet(label_style) - self.thermal_coeff_combo = QComboBox() - self.thermal_coeff_combo.addItems(["12 × 10⁻⁶ /°C", "11.7 × 10⁻⁶ /°C", "Custom"]) - self.thermal_coeff_combo.setFixedWidth(field_width) - apply_field_style(self.thermal_coeff_combo) - coeff_layout.addWidget(lbl, 0, 0, Qt.AlignLeft | Qt.AlignVCenter) - coeff_layout.addWidget(self.thermal_coeff_combo, 0, 1, Qt.AlignLeft) - - left_layout.addWidget(coeff_box) - left_layout.addStretch() - - # Right description card - right_card = self._create_card() - right_card.setStyleSheet("QFrame { border: 1px solid #9c9c9c; border-radius: 10px; background-color: #d4d4d4; }") - right_card.setMinimumWidth(200) - right_card.setMinimumHeight(400) - right_layout = QVBoxLayout(right_card) - right_layout.setContentsMargins(16, 16, 16, 16) - right_layout.setSpacing(10) - - desc_title = QLabel("Description Box") - desc_title.setAlignment(Qt.AlignCenter) - desc_title.setStyleSheet("font-size: 12px; font-weight: 700; color: #2b2b2b; background: transparent; border: none;") - right_layout.addWidget(desc_title) - right_layout.addStretch() - - content_row.addWidget(left_card, 3) - content_row.addWidget(right_card, 2) - - page_layout.addLayout(content_row) - - return page - - def _build_load_combination_tab(self): - """Build the Load Combination tab matching reference design""" - page = QWidget() - page.setStyleSheet("background-color: #f5f5f5;") - page_layout = QVBoxLayout(page) - page_layout.setContentsMargins(12, 12, 12, 12) - page_layout.setSpacing(12) - - content_row = QHBoxLayout() - content_row.setContentsMargins(0, 0, 0, 0) - content_row.setSpacing(16) - - label_style = "font-size: 11px; color: #3a3a3a; background: transparent; border: none;" - - # Left card - combination list - left_card = self._create_card() - left_card.setStyleSheet("QFrame { border: 1px solid #b2b2b2; border-radius: 10px; background-color: #ffffff; }") - left_layout = QVBoxLayout(left_card) - left_layout.setContentsMargins(16, 16, 16, 16) - left_layout.setSpacing(12) - - # Auto include checkbox row - auto_row = QHBoxLayout() - auto_row.setContentsMargins(0, 0, 0, 0) - auto_row.setSpacing(8) - auto_label = QLabel("Auto include all IRC 6 Load Combinations") - auto_label.setStyleSheet("font-size: 11px; color: #3a3a3a; background: transparent; border: none;") - self.auto_include_checkbox = QCheckBox() - auto_row.addWidget(auto_label) - auto_row.addWidget(self.auto_include_checkbox) - auto_row.addStretch() - left_layout.addLayout(auto_row) - - # Combination Name label - combo_name_label = QLabel("Combination Name") - combo_name_label.setStyleSheet("font-size: 12px; font-weight: 700; color: #2b2b2b; background: transparent; border: none;") - left_layout.addWidget(combo_name_label) - - # Combination list area - self.combination_list_widget = QWidget() - self.combination_list_widget.setStyleSheet("background: #ffffff;") - self.combination_list_layout = QVBoxLayout(self.combination_list_widget) - self.combination_list_layout.setContentsMargins(0, 8, 0, 8) - self.combination_list_layout.setSpacing(8) - - # Add sample combinations - sample_combos = ["DL + LL", "1.35 DL + 1.75 LL"] - for combo_text in sample_combos: - combo_label = QLabel(combo_text) - combo_label.setStyleSheet(label_style) - self.combination_list_layout.addWidget(combo_label) - - self.combination_list_layout.addStretch() - left_layout.addWidget(self.combination_list_widget, 1) - - # Middle card - combination editor - middle_card = self._create_card() - middle_card.setStyleSheet("QFrame { border: 1px solid #b2b2b2; border-radius: 10px; background-color: #ffffff; }") - middle_layout = QVBoxLayout(middle_card) - middle_layout.setContentsMargins(16, 16, 16, 16) - middle_layout.setSpacing(12) - - # Combination Name title - combo_title = QLabel("Combination Name") - combo_title.setStyleSheet("font-size: 12px; font-weight: 700; color: #2b2b2b; background: transparent; border: none;") - middle_layout.addWidget(combo_title) - - # Editor area with table and buttons - editor_row = QHBoxLayout() - editor_row.setContentsMargins(0, 0, 0, 0) - editor_row.setSpacing(12) - - # Table for Load Name and Scale Factor - table_box = QFrame() - table_box.setStyleSheet("QFrame { border: 1px solid #b2b2b2; border-radius: 4px; background-color: #ffffff; }") - table_layout = QVBoxLayout(table_box) - table_layout.setContentsMargins(0, 0, 0, 0) - table_layout.setSpacing(0) - - # Header row - header_widget = QWidget() - header_widget.setStyleSheet("background: #ffffff; border-bottom: 1px solid #b2b2b2;") - header_layout = QHBoxLayout(header_widget) - header_layout.setContentsMargins(8, 8, 8, 8) - header_layout.setSpacing(8) - - load_name_header = QLabel("Load Name") - load_name_header.setStyleSheet("font-size: 11px; font-weight: 600; color: #3a3a3a; background: transparent; border: none;") - load_name_header.setMinimumWidth(80) - scale_factor_header = QLabel("Scale Factor") - scale_factor_header.setStyleSheet("font-size: 11px; font-weight: 600; color: #3a3a3a; background: transparent; border: none;") - scale_factor_header.setMinimumWidth(80) - - header_layout.addWidget(load_name_header) - header_layout.addWidget(scale_factor_header) - table_layout.addWidget(header_widget) - - # Input row - input_widget = QWidget() - input_widget.setStyleSheet("background: #ffffff;") - input_layout = QHBoxLayout(input_widget) - input_layout.setContentsMargins(8, 8, 8, 8) - input_layout.setSpacing(8) - - self.load_name_combo = QComboBox() - self.load_name_combo.addItems(["DL", "LL", "WL", "EL", "TL"]) - self.load_name_combo.setMinimumWidth(80) - apply_field_style(self.load_name_combo) - - self.scale_factor_input = QLineEdit() - self.scale_factor_input.setMinimumWidth(80) - apply_field_style(self.scale_factor_input) - - input_layout.addWidget(self.load_name_combo) - input_layout.addWidget(self.scale_factor_input) - table_layout.addWidget(input_widget) - - # Empty space for more rows - table_layout.addStretch() - - editor_row.addWidget(table_box, 1) - - # Add/Delete buttons column - button_col = QVBoxLayout() - button_col.setContentsMargins(0, 0, 0, 0) - button_col.setSpacing(8) - - self.add_load_btn = QPushButton("Add") - self.add_load_btn.setFixedWidth(60) - self.add_load_btn.setStyleSheet( - "QPushButton { background: #ffffff; border: 1px solid #b2b2b2; border-radius: 4px; padding: 6px 12px; font-size: 11px; color: #3a3a3a; }" - "QPushButton:hover { background: #f0f0f0; }" - "QPushButton:pressed { background: #e0e0e0; }" - ) + self.lowest_min_temp_input.setFixedWidth(field_width) + apply_field_style(self.lowest_min_temp_input) + irc6_grid.addWidget(lbl, 1, 0, Qt.AlignLeft | Qt.AlignVCenter) + irc6_grid.addWidget(self.lowest_min_temp_input, 1, 1, Qt.AlignLeft) - self.delete_load_btn = QPushButton("Delete") - self.delete_load_btn.setFixedWidth(60) - self.delete_load_btn.setStyleSheet( - "QPushButton { background: #ffffff; border: 1px solid #b2b2b2; border-radius: 4px; padding: 6px 12px; font-size: 11px; color: #3a3a3a; }" - "QPushButton:hover { background: #f0f0f0; }" - "QPushButton:pressed { background: #e0e0e0; }" - ) + irc6_layout.addLayout(irc6_grid) + left_layout.addWidget(irc6_box) - button_col.addWidget(self.add_load_btn) - button_col.addWidget(self.delete_load_btn) - button_col.addStretch() + # ===== Range of Effective Bridge Temperature Box ===== + range_box = QFrame() + range_box.setStyleSheet("QFrame { border: 1px solid #b2b2b2; border-radius: 8px; background-color: #ffffff; }") + range_layout = QVBoxLayout(range_box) + range_layout.setContentsMargins(12, 12, 12, 12) + range_layout.setSpacing(10) - editor_row.addLayout(button_col) - middle_layout.addLayout(editor_row, 1) + range_title = QLabel("Range of Effective Bridge Temperature:") + range_title.setStyleSheet("font-size: 12px; font-weight: 700; color: #2b2b2b; background: transparent; border: none;") + range_layout.addWidget(range_title) - content_row.addWidget(left_card, 2) - content_row.addWidget(middle_card, 3) + range_grid = QGridLayout() + range_grid.setContentsMargins(0, 4, 0, 0) + range_grid.setHorizontalSpacing(12) + range_grid.setVerticalSpacing(10) + range_grid.setColumnMinimumWidth(0, 200) - page_layout.addLayout(content_row) + # Minimum + lbl = QLabel("Minimum:") + lbl.setStyleSheet(label_style) + self.bridge_temp_min_input = QLineEdit() + self.bridge_temp_min_input.setFixedWidth(field_width) + apply_field_style(self.bridge_temp_min_input) + range_grid.addWidget(lbl, 0, 0, Qt.AlignLeft | Qt.AlignVCenter) + range_grid.addWidget(self.bridge_temp_min_input, 0, 1, Qt.AlignLeft) - return page + # Maximum + lbl = QLabel("Maximum:") + lbl.setStyleSheet(label_style) + self.bridge_temp_max_input = QLineEdit() + self.bridge_temp_max_input.setFixedWidth(field_width) + apply_field_style(self.bridge_temp_max_input) + range_grid.addWidget(lbl, 1, 0, Qt.AlignLeft | Qt.AlignVCenter) + range_grid.addWidget(self.bridge_temp_max_input, 1, 1, Qt.AlignLeft) - def _create_placeholder_page(self, title): - page = QWidget() - page.setStyleSheet("background-color: #f5f5f5;") - layout = QVBoxLayout(page) - layout.setAlignment(Qt.AlignCenter) - layout.setContentsMargins(20, 20, 20, 20) + range_layout.addLayout(range_grid) + left_layout.addWidget(range_box) - label = QLabel(f"{title} inputs will be added soon.") - label.setAlignment(Qt.AlignCenter) - label.setStyleSheet("font-size: 12px; color: #6a6a6a;") - layout.addWidget(label) - return page + # ===== Coefficient of Thermal Expansion Box ===== + coeff_box = QFrame() + coeff_box.setStyleSheet("QFrame { border: 1px solid #b2b2b2; border-radius: 8px; background-color: #ffffff; }") + coeff_layout = QGridLayout(coeff_box) + coeff_layout.setContentsMargins(12, 12, 12, 12) + coeff_layout.setHorizontalSpacing(12) + coeff_layout.setVerticalSpacing(10) + coeff_layout.setColumnMinimumWidth(0, 200) + lbl = QLabel("Coefficient of Thermal Expansion for Steel:") + lbl.setStyleSheet(label_style) + self.thermal_coeff_combo = QComboBox() + self.thermal_coeff_combo.addItems(["12 × 10⁻⁶ /°C", "11.7 × 10⁻⁶ /°C", "Custom"]) + self.thermal_coeff_combo.setFixedWidth(field_width) + apply_field_style(self.thermal_coeff_combo) + coeff_layout.addWidget(lbl, 0, 0, Qt.AlignLeft | Qt.AlignVCenter) + coeff_layout.addWidget(self.thermal_coeff_combo, 0, 1, Qt.AlignLeft) -class StretchingTabBar(QTabBar): - """Tab bar that distributes tab widths across available space.""" + left_layout.addWidget(coeff_box) + left_layout.addStretch() - def tabSizeHint(self, index): - hint = super().tabSizeHint(index) - count = max(1, self.count()) - available_width = max(self.width(), hint.width() * count) - stretched_width = max(hint.width(), available_width // count) - return QSize(stretched_width, hint.height()) + # Right description card + right_card = self._create_card() + right_card.setStyleSheet("QFrame { border: 1px solid #9c9c9c; border-radius: 10px; background-color: #d4d4d4; }") + right_card.setMinimumWidth(200) + right_card.setMinimumHeight(400) + right_layout = QVBoxLayout(right_card) + right_layout.setContentsMargins(16, 16, 16, 16) + right_layout.setSpacing(10) - def minimumTabSizeHint(self, index): - return self.tabSizeHint(index) + desc_title = QLabel("Description Box") + desc_title.setAlignment(Qt.AlignCenter) + desc_title.setStyleSheet("font-size: 12px; font-weight: 700; color: #2b2b2b; background: transparent; border: none;") + right_layout.addWidget(desc_title) + right_layout.addStretch() - def resizeEvent(self, event): - super().resizeEvent(event) - self.updateGeometry() + content_row.addWidget(left_card, 3) + content_row.addWidget(right_card, 2) + page_layout.addLayout(content_row) -class AdditionalInputs(QWidget): - """Main widget for Additional Inputs with tabbed interface""" - - def __init__(self, footpath_value="None", carriageway_width=7.5, parent=None): - super().__init__(parent) - self.setObjectName("AdditionalInputs") - self.footpath_value = footpath_value - self.carriageway_width = carriageway_width - self.init_ui() - - def init_ui(self): - # Set explicit white background to prevent black background issue - self.setStyleSheet("QWidget#AdditionalInputs { background-color: #ffffff; }") - self.setAttribute(Qt.WA_StyledBackground, True) - - main_layout = QVBoxLayout(self) - main_layout.setContentsMargins(5, 5, 5, 5) - - # Main tab widget - self.tabs = QTabWidget() - self.tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) - self.stretching_tab_bar = StretchingTabBar() - self.stretching_tab_bar.setElideMode(Qt.ElideRight) - self.tabs.setTabBar(self.stretching_tab_bar) - self.tabs.setStyleSheet(""" - QTabWidget::pane { - border: 1px solid #d1d1d1; - background-color: #ffffff; - border-radius: 6px; - } - QTabBar::tab { - font-weight: bold; - font-size: 12px; - background: #ffffff; - color: #3a3a3a; - border: 1px solid #d1d1d1; - padding: 10px 22px; - } - QTabBar::tab:selected { - background: #90AF13; - color: #ffffff; - border: 1px solid #90AF13; - } - QTabBar::tab:hover { - background: #90AF13; - color: #ffffff; - } - """) - - # Sub-Tab 1: Typical Section Details - self.typical_section_tab = TypicalSectionDetailsTab(self.footpath_value, self.carriageway_width) - self.tabs.addTab(self.typical_section_tab, "Typical Section Details") - - # Sub-Tab 2: Member Properties - self.section_properties_tab = SectionPropertiesTab() - self.tabs.addTab(self.section_properties_tab, "Member Properties") - - # Sub-Tab 3: Loading - self.loading_tab = LoadingTab() - self.tabs.addTab(self.loading_tab, "Loading") - - # Sub-Tab 4: Support Conditions - support_tab = self._build_support_conditions_tab() - self.tabs.addTab(support_tab, "Support Conditions") - - # Sub-Tab 5: Design Options - design_options_tab = self._build_design_options_tab() - self.tabs.addTab(design_options_tab, "Design Options") - - # Sub-Tab 6: Design Options (Cont.) - analysis_design_tab = self.create_placeholder_tab( - "Design Options (Cont.)", - "This tab will contain:\n\n" + - "• Analysis Method\n" + - "• Design Code Options\n" + - "• Safety Factors\n" + - "• Other Design Parameters\n\n" + - "Implementation in progress..." - ) - self.tabs.addTab(analysis_design_tab, "Design Options (Cont.)") - - main_layout.addWidget(self.tabs) + return page - def _build_support_conditions_tab(self): - """Build the Support Conditions tab matching reference design""" - widget = QWidget() - widget.setStyleSheet("background-color: #f5f5f5;") - - main_layout = QVBoxLayout(widget) - main_layout.setContentsMargins(12, 12, 12, 12) - main_layout.setSpacing(12) + def _build_load_combination_tab(self): + """Build the Load Combination tab matching reference design""" + page = QWidget() + page.setStyleSheet("background-color: #f5f5f5;") + page_layout = QVBoxLayout(page) + page_layout.setContentsMargins(12, 12, 12, 12) + page_layout.setSpacing(12) - # Main card - card = QFrame() - card.setStyleSheet("QFrame { border: 1px solid #b2b2b2; border-radius: 10px; background-color: #ffffff; }") - card_layout = QVBoxLayout(card) - card_layout.setContentsMargins(16, 16, 16, 16) - card_layout.setSpacing(16) + content_row = QHBoxLayout() + content_row.setContentsMargins(0, 0, 0, 0) + content_row.setSpacing(16) label_style = "font-size: 11px; color: #3a3a3a; background: transparent; border: none;" - heading_style = "font-size: 12px; font-weight: 700; color: #2b2b2b; background: transparent; border: none;" - field_width = 120 - # Support Condition section - support_title = QLabel("Support Condition*") - support_title.setStyleSheet(heading_style) - card_layout.addWidget(support_title) + # Left card - combination list + left_card = self._create_card() + left_card.setStyleSheet("QFrame { border: 1px solid #b2b2b2; border-radius: 10px; background-color: #ffffff; }") + left_layout = QVBoxLayout(left_card) + left_layout.setContentsMargins(16, 16, 16, 16) + left_layout.setSpacing(12) - support_grid = QGridLayout() - support_grid.setContentsMargins(0, 8, 0, 0) - support_grid.setHorizontalSpacing(12) - support_grid.setVerticalSpacing(12) - support_grid.setColumnMinimumWidth(0, 120) + # Auto include checkbox row + auto_row = QHBoxLayout() + auto_row.setContentsMargins(0, 0, 0, 0) + auto_row.setSpacing(8) + auto_label = QLabel("Auto include all IRC 6 Load Combinations") + auto_label.setStyleSheet("font-size: 11px; color: #3a3a3a; background: transparent; border: none;") + self.auto_include_checkbox = QCheckBox() + auto_row.addWidget(auto_label) + auto_row.addWidget(self.auto_include_checkbox) + auto_row.addStretch() + left_layout.addLayout(auto_row) - # Left Support - lbl = QLabel("Left Support:") - lbl.setStyleSheet(label_style) - self.left_support_combo = QComboBox() - self.left_support_combo.addItems(["Fixed", "Pinned", "Roller"]) - self.left_support_combo.setFixedWidth(field_width) - apply_field_style(self.left_support_combo) - support_grid.addWidget(lbl, 0, 0, Qt.AlignLeft | Qt.AlignVCenter) - support_grid.addWidget(self.left_support_combo, 0, 1, Qt.AlignLeft) + # Combination Name label + combo_name_label = QLabel("Combination Name") + combo_name_label.setStyleSheet("font-size: 12px; font-weight: 700; color: #2b2b2b; background: transparent; border: none;") + left_layout.addWidget(combo_name_label) - # Right Support - lbl = QLabel("Right Support:") - lbl.setStyleSheet(label_style) - self.right_support_combo = QComboBox() - self.right_support_combo.addItems(["Fixed", "Pinned", "Roller"]) - self.right_support_combo.setFixedWidth(field_width) - apply_field_style(self.right_support_combo) - support_grid.addWidget(lbl, 1, 0, Qt.AlignLeft | Qt.AlignVCenter) - support_grid.addWidget(self.right_support_combo, 1, 1, Qt.AlignLeft) + # Combination list area + self.combination_list_widget = QWidget() + self.combination_list_widget.setStyleSheet("background: #ffffff;") + self.combination_list_layout = QVBoxLayout(self.combination_list_widget) + self.combination_list_layout.setContentsMargins(0, 8, 0, 8) + self.combination_list_layout.setSpacing(8) - card_layout.addLayout(support_grid) + # Add sample combinations + sample_combos = ["DL + LL", "1.35 DL + 1.75 LL"] + for combo_text in sample_combos: + combo_label = QLabel(combo_text) + combo_label.setStyleSheet(label_style) + self.combination_list_layout.addWidget(combo_label) - # Bearing Length section - bearing_title = QLabel("Bearing length*") - bearing_title.setStyleSheet(heading_style) - card_layout.addWidget(bearing_title) + self.combination_list_layout.addStretch() + left_layout.addWidget(self.combination_list_widget, 1) - bearing_grid = QGridLayout() - bearing_grid.setContentsMargins(0, 8, 0, 0) - bearing_grid.setHorizontalSpacing(12) - bearing_grid.setVerticalSpacing(12) - bearing_grid.setColumnMinimumWidth(0, 120) + # Middle card - combination editor + middle_card = self._create_card() + middle_card.setStyleSheet("QFrame { border: 1px solid #b2b2b2; border-radius: 10px; background-color: #ffffff; }") + middle_layout = QVBoxLayout(middle_card) + middle_layout.setContentsMargins(16, 16, 16, 16) + middle_layout.setSpacing(12) - lbl = QLabel("Bearing Length Value") - lbl.setStyleSheet(label_style) - self.bearing_length_input = QLineEdit() - self.bearing_length_input.setText("0") - self.bearing_length_input.setFixedWidth(field_width) - apply_field_style(self.bearing_length_input) - bearing_grid.addWidget(lbl, 0, 0, Qt.AlignLeft | Qt.AlignVCenter) - bearing_grid.addWidget(self.bearing_length_input, 0, 1, Qt.AlignLeft) + # Combination Name title + combo_title = QLabel("Combination Name") + combo_title.setStyleSheet("font-size: 12px; font-weight: 700; color: #2b2b2b; background: transparent; border: none;") + middle_layout.addWidget(combo_title) - card_layout.addLayout(bearing_grid) - card_layout.addStretch() + # Editor area with table and buttons + editor_row = QHBoxLayout() + editor_row.setContentsMargins(0, 0, 0, 0) + editor_row.setSpacing(12) - main_layout.addWidget(card) - main_layout.addStretch() + # Table for Load Name and Scale Factor + table_box = QFrame() + table_box.setStyleSheet("QFrame { border: 1px solid #b2b2b2; border-radius: 4px; background-color: #ffffff; }") + table_layout = QVBoxLayout(table_box) + table_layout.setContentsMargins(0, 0, 0, 0) + table_layout.setSpacing(0) - return widget + # Header row + header_widget = QWidget() + header_widget.setStyleSheet("background: #ffffff; border-bottom: 1px solid #b2b2b2;") + header_layout = QHBoxLayout(header_widget) + header_layout.setContentsMargins(8, 8, 8, 8) + header_layout.setSpacing(8) - def _build_design_options_tab(self): - """Build the Design Options tab matching reference design""" - widget = QWidget() - widget.setStyleSheet("background-color: #f5f5f5;") - - main_layout = QVBoxLayout(widget) - main_layout.setContentsMargins(12, 12, 12, 12) - main_layout.setSpacing(12) + load_name_header = QLabel("Load Name") + load_name_header.setStyleSheet("font-size: 11px; font-weight: 600; color: #3a3a3a; background: transparent; border: none;") + load_name_header.setMinimumWidth(80) + scale_factor_header = QLabel("Scale Factor") + scale_factor_header.setStyleSheet("font-size: 11px; font-weight: 600; color: #3a3a3a; background: transparent; border: none;") + scale_factor_header.setMinimumWidth(80) - # Main card - card = QFrame() - card.setStyleSheet("QFrame { border: 1px solid #b2b2b2; border-radius: 10px; background-color: #ffffff; }") - card_layout = QVBoxLayout(card) - card_layout.setContentsMargins(16, 16, 16, 16) - card_layout.setSpacing(12) + header_layout.addWidget(load_name_header) + header_layout.addWidget(scale_factor_header) + table_layout.addWidget(header_widget) - label_style = "font-size: 11px; color: #3a3a3a; background: transparent; border: none;" - heading_style = "font-size: 12px; font-weight: 700; color: #2b2b2b; background: transparent; border: none;" - field_width = 120 + # Input row + input_widget = QWidget() + input_widget.setStyleSheet("background: #ffffff;") + input_layout = QHBoxLayout(input_widget) + input_layout.setContentsMargins(8, 8, 8, 8) + input_layout.setSpacing(8) - # Deck Design section - deck_title = QLabel("Deck Design:") - deck_title.setStyleSheet(heading_style) - card_layout.addWidget(deck_title) + self.load_name_combo = QComboBox() + self.load_name_combo.addItems(["DL", "LL", "WL", "EL", "TL"]) + self.load_name_combo.setMinimumWidth(80) + apply_field_style(self.load_name_combo) - deck_grid = QGridLayout() - deck_grid.setContentsMargins(0, 4, 0, 0) - deck_grid.setHorizontalSpacing(12) - deck_grid.setVerticalSpacing(10) - deck_grid.setColumnMinimumWidth(0, 120) + self.scale_factor_input = QLineEdit() + self.scale_factor_input.setMinimumWidth(80) + apply_field_style(self.scale_factor_input) - lbl = QLabel("Reinforcement Size:") - lbl.setStyleSheet(label_style) - self.reinforcement_size_combo = QComboBox() - self.reinforcement_size_combo.addItems(["8 mm", "10 mm", "12 mm", "16 mm", "20 mm"]) - self.reinforcement_size_combo.setFixedWidth(field_width) - apply_field_style(self.reinforcement_size_combo) - deck_grid.addWidget(lbl, 0, 0, Qt.AlignLeft | Qt.AlignVCenter) - deck_grid.addWidget(self.reinforcement_size_combo, 0, 1, Qt.AlignLeft) + input_layout.addWidget(self.load_name_combo) + input_layout.addWidget(self.scale_factor_input) + table_layout.addWidget(input_widget) - card_layout.addLayout(deck_grid) + # Empty space for more rows + table_layout.addStretch() - # Shear Studs section - shear_title = QLabel("Shear Studs:") - shear_title.setStyleSheet(heading_style) - card_layout.addWidget(shear_title) + editor_row.addWidget(table_box, 1) - shear_grid = QGridLayout() - shear_grid.setContentsMargins(0, 4, 0, 0) - shear_grid.setHorizontalSpacing(12) - shear_grid.setVerticalSpacing(10) - shear_grid.setColumnMinimumWidth(0, 120) + # Add/Delete buttons column + button_col = QVBoxLayout() + button_col.setContentsMargins(0, 0, 0, 0) + button_col.setSpacing(8) - # Material - lbl = QLabel("Material:") - lbl.setStyleSheet(label_style) - self.shear_stud_material_input = QLineEdit() - self.shear_stud_material_input.setFixedWidth(field_width) - apply_field_style(self.shear_stud_material_input) - shear_grid.addWidget(lbl, 0, 0, Qt.AlignLeft | Qt.AlignVCenter) - shear_grid.addWidget(self.shear_stud_material_input, 0, 1, Qt.AlignLeft) + self.add_load_btn = QPushButton("Add") + self.add_load_btn.setFixedWidth(60) + self.add_load_btn.setStyleSheet( + "QPushButton { background: #ffffff; border: 1px solid #b2b2b2; border-radius: 4px; padding: 6px 12px; font-size: 11px; color: #3a3a3a; }" + "QPushButton:hover { background: #f0f0f0; }" + "QPushButton:pressed { background: #e0e0e0; }" + ) - # Diameter - lbl = QLabel("Diameter (mm):") - lbl.setStyleSheet(label_style) - self.shear_stud_diameter_input = QLineEdit() - self.shear_stud_diameter_input.setFixedWidth(field_width) - apply_field_style(self.shear_stud_diameter_input) - shear_grid.addWidget(lbl, 1, 0, Qt.AlignLeft | Qt.AlignVCenter) - shear_grid.addWidget(self.shear_stud_diameter_input, 1, 1, Qt.AlignLeft) + self.delete_load_btn = QPushButton("Delete") + self.delete_load_btn.setFixedWidth(60) + self.delete_load_btn.setStyleSheet( + "QPushButton { background: #ffffff; border: 1px solid #b2b2b2; border-radius: 4px; padding: 6px 12px; font-size: 11px; color: #3a3a3a; }" + "QPushButton:hover { background: #f0f0f0; }" + "QPushButton:pressed { background: #e0e0e0; }" + ) - # Height - lbl = QLabel("Height (mm):") - lbl.setStyleSheet(label_style) - self.shear_stud_height_input = QLineEdit() - self.shear_stud_height_input.setFixedWidth(field_width) - apply_field_style(self.shear_stud_height_input) - shear_grid.addWidget(lbl, 2, 0, Qt.AlignLeft | Qt.AlignVCenter) - shear_grid.addWidget(self.shear_stud_height_input, 2, 1, Qt.AlignLeft) + button_col.addWidget(self.add_load_btn) + button_col.addWidget(self.delete_load_btn) + button_col.addStretch() - card_layout.addLayout(shear_grid) - card_layout.addStretch() + editor_row.addLayout(button_col) + middle_layout.addLayout(editor_row, 1) - main_layout.addWidget(card) - main_layout.addStretch() + content_row.addWidget(left_card, 2) + content_row.addWidget(middle_card, 3) - return widget - - def create_placeholder_tab(self, title, description): - """Create a styled placeholder tab with title and description""" - widget = QWidget() - widget.setStyleSheet("background-color: white;") - - layout = QVBoxLayout(widget) + page_layout.addLayout(content_row) + + return page + + def _create_placeholder_page(self, title): + page = QWidget() + page.setStyleSheet("background-color: #f5f5f5;") + layout = QVBoxLayout(page) layout.setAlignment(Qt.AlignCenter) - layout.setContentsMargins(40, 40, 40, 40) - - # Icon or visual indicator - icon_label = QLabel("🚧") - icon_label.setStyleSheet("font-size: 48px;") - icon_label.setAlignment(Qt.AlignCenter) - layout.addWidget(icon_label) - - # Title - title_label = QLabel(title) - title_label.setStyleSheet(""" - font-size: 18px; - font-weight: bold; - color: #333; - margin-top: 20px; - margin-bottom: 10px; - """) - title_label.setAlignment(Qt.AlignCenter) - layout.addWidget(title_label) - - # Status - status_label = QLabel("Under Development") - status_label.setStyleSheet(""" - font-size: 14px; - color: #f39c12; - font-weight: bold; - margin-bottom: 20px; - """) - status_label.setAlignment(Qt.AlignCenter) - layout.addWidget(status_label) - - # Description - desc_label = QLabel(description) - desc_label.setStyleSheet(""" - font-size: 12px; - color: #666; - line-height: 1.6; - """) - desc_label.setAlignment(Qt.AlignCenter) - desc_label.setWordWrap(True) - desc_label.setMaximumWidth(600) - layout.addWidget(desc_label) - - layout.addStretch() - layout.addSpacing(10) - action_bar, _, _ = create_action_button_bar(widget) - layout.addWidget(action_bar) - - return widget - - def update_footpath_value(self, footpath_value): - """Update footpath value across all tabs""" - self.footpath_value = footpath_value - self.typical_section_tab.update_footpath_value(footpath_value) + layout.setContentsMargins(20, 20, 20, 20) + + label = QLabel(f"{title} inputs will be added soon.") + label.setAlignment(Qt.AlignCenter) + label.setStyleSheet("font-size: 12px; color: #6a6a6a;") + layout.addWidget(label) + return page diff --git a/src/osdagbridge/desktop/ui/docks/input_dock.py b/src/osdagbridge/desktop/ui/docks/input_dock.py index 245d1350..310e1961 100644 --- a/src/osdagbridge/desktop/ui/docks/input_dock.py +++ b/src/osdagbridge/desktop/ui/docks/input_dock.py @@ -632,7 +632,7 @@ def __init__(self, backend, parent): self.custom_location_input = None self.include_median_combo = None self.footpath_combo = None - self.additional_inputs_window = None + self.additional_inputs = None self.additional_inputs_widget = None self.material_dialog = None self.additional_inputs_btn = None @@ -1604,34 +1604,8 @@ def show_additional_inputs(self): carriageway_width = self._get_effective_carriageway_width() - if self.additional_inputs_window is None or not self.additional_inputs_window.isVisible(): - self.additional_inputs_window = QDialog(self) - self.additional_inputs_window.setObjectName("AdditionalInputs") - self.additional_inputs_window.setWindowTitle("Additional Inputs - Manual Bridge Parameter Definition") - self.additional_inputs_window.resize(1024, 720) - self.additional_inputs_window.setMinimumSize(820, 520) - self.additional_inputs_window.setSizeGripEnabled(True) - - layout = QVBoxLayout(self.additional_inputs_window) - layout.setContentsMargins(0, 0, 0, 0) - - scroll_area = QScrollArea(self.additional_inputs_window) - scroll_area.setWidgetResizable(True) - scroll_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) - scroll_area.setFrameShape(QFrame.NoFrame) - scroll_area.setStyleSheet("QScrollArea { border: none; background: transparent; }") - layout.addWidget(scroll_area) - - self.additional_inputs_widget = AdditionalInputs(footpath_value, carriageway_width) - scroll_area.setWidget(self.additional_inputs_widget) - self.additional_inputs_window.destroyed.connect(lambda _=None: self._handle_additional_inputs_closed()) - self._set_additional_inputs_enabled(not self.is_locked) - - self.additional_inputs_window.show() - else: - self.additional_inputs_window.raise_() - self.additional_inputs_window.activateWindow() - self._set_additional_inputs_enabled(not self.is_locked) + self.additional_inputs = AdditionalInputs(footpath_value, carriageway_width) + self.additional_inputs.show() def _apply_lock_state(self): self.update_lock_icon() @@ -1651,12 +1625,12 @@ def _set_additional_inputs_enabled(self, enabled): self.additional_inputs_widget.setEnabled(enabled) def _handle_additional_inputs_closed(self): - self.additional_inputs_window = None + self.additional_inputs = None self.additional_inputs_widget = None def on_footpath_changed(self, footpath_value): """Update additional inputs when footpath changes""" - if self.additional_inputs_window and self.additional_inputs_window.isVisible(): + if self.additional_inputs and self.additional_inputs.isVisible(): if hasattr(self, 'additional_inputs_widget'): self.additional_inputs_widget.update_footpath_value(footpath_value) diff --git a/src/osdagbridge/desktop/ui/docks/log_dock.py b/src/osdagbridge/desktop/ui/docks/log_dock.py index 9e0ac748..37f28d33 100644 --- a/src/osdagbridge/desktop/ui/docks/log_dock.py +++ b/src/osdagbridge/desktop/ui/docks/log_dock.py @@ -67,19 +67,16 @@ def adjust_size(self): parent = self.parent() if not parent: return - - # Get the current tab's input and output dock states - current_tab_index = parent.tab_bar.currentIndex() - if current_tab_index < 0 or current_tab_index >= len(parent.tab_widget_content): + if parent.input_dock is None or parent.output_dock is None: return - input_active = parent.tab_widget_content[current_tab_index][3] - output_active = parent.tab_widget_content[current_tab_index][4] + input_dock = parent.input_dock + output_dock = parent.output_dock # Calculate available width parent_width = parent.width() - input_dock_width = parent.tab_widget_content[current_tab_index][1].width() if input_active else 0 - output_dock_width = parent.tab_widget_content[current_tab_index][2].width() if output_active else 0 + input_dock_width = input_dock.width() if input_dock.isVisible() else 0 + output_dock_width = output_dock.width() if output_dock.isVisible() else 0 available_width = parent_width - input_dock_width - output_dock_width # Set log dock size diff --git a/src/osdagbridge/desktop/ui/template_page.py b/src/osdagbridge/desktop/ui/template_page.py index 262cdf37..c0a552e9 100644 --- a/src/osdagbridge/desktop/ui/template_page.py +++ b/src/osdagbridge/desktop/ui/template_page.py @@ -65,6 +65,8 @@ def __init__(self, title: str, backend: object, parent=None): } """ ) + self.input_dock = None + self.output_dock = None self.init_ui() @@ -164,7 +166,7 @@ def mousePressEvent(self, event): # Add Cad Model Widget self.cad_log_splitter.addWidget(self.cad_comp_widget) - self.logs_dock = LogDock() + self.logs_dock = LogDock(parent=self) self.logs_dock.setVisible(False) # log text self.textEdit = self.logs_dock.log_display From 421af42195ff3adcc9c438276028fcd31b9c851a Mon Sep 17 00:00:00 2001 From: Aditya-Donde Date: Tue, 16 Dec 2025 11:23:10 +0530 Subject: [PATCH 49/59] Added Database --- .../core/data/ResourceFiles/Intg_osdag.sql | 1360 +++++++++++++++++ .../core/data/ResourceFiles/Intg_osdag.sqlite | Bin 0 -> 249856 bytes .../core/data/ResourceFiles/precision.awk | 81 + 3 files changed, 1441 insertions(+) create mode 100644 src/osdagbridge/core/data/ResourceFiles/Intg_osdag.sql create mode 100644 src/osdagbridge/core/data/ResourceFiles/Intg_osdag.sqlite create mode 100644 src/osdagbridge/core/data/ResourceFiles/precision.awk diff --git a/src/osdagbridge/core/data/ResourceFiles/Intg_osdag.sql b/src/osdagbridge/core/data/ResourceFiles/Intg_osdag.sql new file mode 100644 index 00000000..8000cd4f --- /dev/null +++ b/src/osdagbridge/core/data/ResourceFiles/Intg_osdag.sql @@ -0,0 +1,1360 @@ +PRAGMA foreign_keys=OFF; +BEGIN TRANSACTION; +CREATE TABLE IF NOT EXISTS "Bolt" ( + "Bolt_diameter" TEXT +); +INSERT INTO Bolt VALUES('8'); +INSERT INTO Bolt VALUES('10'); +INSERT INTO Bolt VALUES('12'); +INSERT INTO Bolt VALUES('16'); +INSERT INTO Bolt VALUES('20'); +INSERT INTO Bolt VALUES('24'); +INSERT INTO Bolt VALUES('30'); +INSERT INTO Bolt VALUES('36'); +INSERT INTO Bolt VALUES('42'); +INSERT INTO Bolt VALUES('48'); +INSERT INTO Bolt VALUES('56'); +INSERT INTO Bolt VALUES('64'); +INSERT INTO Bolt VALUES('14'); +INSERT INTO Bolt VALUES('18'); +INSERT INTO Bolt VALUES('22'); +INSERT INTO Bolt VALUES('27'); +INSERT INTO Bolt VALUES('33'); +INSERT INTO Bolt VALUES('39'); +INSERT INTO Bolt VALUES('45'); +INSERT INTO Bolt VALUES('52'); +INSERT INTO Bolt VALUES('60'); +CREATE TABLE IF NOT EXISTS "Anchor_Bolt" ( + "Diameter" INTEGER +); +INSERT INTO Anchor_Bolt VALUES('M8'); +INSERT INTO Anchor_Bolt VALUES('M10'); +INSERT INTO Anchor_Bolt VALUES('M12'); +INSERT INTO Anchor_Bolt VALUES('M16'); +INSERT INTO Anchor_Bolt VALUES('M20'); +INSERT INTO Anchor_Bolt VALUES('M24'); +INSERT INTO Anchor_Bolt VALUES('M30'); +INSERT INTO Anchor_Bolt VALUES('M36'); +INSERT INTO Anchor_Bolt VALUES('M42'); +INSERT INTO Anchor_Bolt VALUES('M48'); +INSERT INTO Anchor_Bolt VALUES('M56'); +INSERT INTO Anchor_Bolt VALUES('M64'); +INSERT INTO Anchor_Bolt VALUES('M72'); +CREATE TABLE IF NOT EXISTS "Angle_Pitch" ( + "ID" INTEGER UNIQUE, + "Nominal_Leg" INTEGER, + "Max_Bolt_Dia" INTEGER, + "Bolt_lines" INTEGER, + "S1" INTEGER, + "S2" INTEGER, + "S3" INTEGER +); +INSERT INTO Angle_Pitch VALUES(1,50,12,1,28,NULL,NULL); +INSERT INTO Angle_Pitch VALUES(2,60,16,1,35,NULL,NULL); +INSERT INTO Angle_Pitch VALUES(3,65,20,1,35,NULL,NULL); +INSERT INTO Angle_Pitch VALUES(4,70,20,1,40,NULL,NULL); +INSERT INTO Angle_Pitch VALUES(5,75,20,1,45,NULL,NULL); +INSERT INTO Angle_Pitch VALUES(6,80,20,1,45,NULL,NULL); +INSERT INTO Angle_Pitch VALUES(7,90,24,1,50,NULL,NULL); +INSERT INTO Angle_Pitch VALUES(8,100,24,1,55,NULL,NULL); +INSERT INTO Angle_Pitch VALUES(9,120,16,2,45,50,NULL); +INSERT INTO Angle_Pitch VALUES(10,125,20,2,45,50,NULL); +INSERT INTO Angle_Pitch VALUES(11,150,20,2,55,55,NULL); +INSERT INTO Angle_Pitch VALUES(13,200,30,2,75,75,NULL); +INSERT INTO Angle_Pitch VALUES(12,200,20,3,55,55,55); +CREATE TABLE IF NOT EXISTS "Material" ( + "Grade" TEXT, + "Yield Stress (< 20)" INTEGER, + "Yield Stress (20 -40)" INTEGER, + "Yield Stress (> 40)" INTEGER, + "Ultimate Tensile Stress" INTEGER, + "Elongation " INTEGER +); +INSERT INTO Material VALUES('E 165 (Fe 290)',165,165,165,290,23); +INSERT INTO Material VALUES('E 250 (Fe 410 W)A',250,240,230,410,23); +INSERT INTO Material VALUES('E 250 (Fe 410 W)B',250,240,230,410,23); +INSERT INTO Material VALUES('E 250 (Fe 410 W)C',250,240,230,410,23); +INSERT INTO Material VALUES('E 300 (Fe 440)',300,290,280,440,22); +INSERT INTO Material VALUES('E 350 (Fe 490)',350,330,320,490,22); +INSERT INTO Material VALUES('E 410 (Fe 540)',410,390,380,540,20); +INSERT INTO Material VALUES('E 450 (Fe 570)D',450,430,420,570,20); +INSERT INTO Material VALUES('E 450 (Fe 590) E',450,430,420,590,20); +INSERT INTO Material VALUES('Cus_400_500_600_1400',400,500,600,1400,20); +CREATE TABLE IF NOT EXISTS "Bolt_fy_fu" ( + `Property_Class` NUMERIC, + `Diameter_min` INTEGER, + `Diameter_max` INTEGER, + `fy` NUMERIC, + `fu` NUMERIC +); +INSERT INTO Bolt_fy_fu VALUES(4.6,0,100,240,400); +INSERT INTO Bolt_fy_fu VALUES(4.8,0,100,340,420); +INSERT INTO Bolt_fy_fu VALUES(5.6,0,100,300,500); +INSERT INTO Bolt_fy_fu VALUES(5.8,0,100,420,520); +INSERT INTO Bolt_fy_fu VALUES(3.6,0,100,190,330); +INSERT INTO Bolt_fy_fu VALUES(6.8,0,100,480,600); +INSERT INTO Bolt_fy_fu VALUES(8.8,0,16,640,800); +INSERT INTO Bolt_fy_fu VALUES(8.8,16,100,660,830); +INSERT INTO Bolt_fy_fu VALUES(9.8,0,100,720,900); +INSERT INTO Bolt_fy_fu VALUES(10.9,0,100,940,1040); +INSERT INTO Bolt_fy_fu VALUES(12.9,0,100,1100,1220); +CREATE TABLE IF NOT EXISTS "UnequalAngle" ( + "Id" INTEGER NOT NULL, + "Designation" VARCHAR(50), + "Mass" REAL(10 , 2), + "Area" REAL(10 , 2), + "a" REAL(10 , 2), + "b" REAL(10 , 2), + "t" REAL(10 , 2), + "R1" REAL(10 , 2), + "R2" REAL(10 , 2), + "Cz" REAL(10 , 2), + "Cy" REAL(10 , 2), + "Iz" REAL(10 , 2), + "Iy" REAL(10 , 2), + "Alpha" REAL(10 , 2), + "Iu(max)" REAL(10 , 2), + "Iv(min)" REAL(10 , 2), + "rz" REAL(10 , 2), + "ry" REAL(10 , 2), + "ru(max)" REAL(10 , 2), + "rv(min)" REAL(10 , 2), + "Zz" REAL(10 , 2), + "Zy" REAL(10 , 2), + "Zpz" REAL(10 , 2), + "Zpy" REAL(10 , 2), + "Source" VARCHAR(100), + "It" REAL(10 , 2), + PRIMARY KEY("Id") +); +INSERT INTO UnequalAngle VALUES(1,'∠ 30 ⅹ 20ⅹ 3',1.14,1.45,30.0,20.0,3.0,4.5,0.0,0.51,0.99,1.29,0.46,1.05,1.47,0.27,0.94,0.56,1.01,0.43,0.64,0.31,1.16,0.56,'IS808_Rev',0.042000000000000001776); +INSERT INTO UnequalAngle VALUES(2,'∠ 30 ⅹ 20ⅹ 4',1.48,1.88,30.0,20.0,4.0,4.5,0.0,0.55,1.04,1.63,0.57,0.4,1.85,0.34,0.93,0.55,0.99,0.43,0.83,0.39,1.48,0.73,'IS808_Rev',0.098000000000000024868); +INSERT INTO UnequalAngle VALUES(3,'∠ 30 ⅹ 20ⅹ 5',1.8,2.29,30.0,20.0,5.0,4.5,0.0,0.58,1.07,1.93,0.67,0.39,2.19,0.41,0.92,0.54,0.98,0.42,1.0,0.47,1.79,0.9,'IS808_Rev',0.18700000000000001065); +INSERT INTO UnequalAngle VALUES(4,'∠ 40 ⅹ 25ⅹ 3',1.5,1.91,40.0,25.0,3.0,5.0,0.0,0.59,1.32,3.11,0.94,0.37,3.48,0.57,1.27,0.7,1.35,0.54,1.16,0.49,2.08,0.9,'IS808_Rev',0.055); +INSERT INTO UnequalAngle VALUES(5,'∠ 40 ⅹ 25ⅹ 4',1.96,2.49,40.0,25.0,4.0,5.0,0.0,0.63,1.36,3.97,1.19,0.36,4.44,0.72,1.26,0.69,1.33,0.54,1.5,0.64,2.69,1.18,'IS808_Rev',0.13); +INSERT INTO UnequalAngle VALUES(6,'∠ 40 ⅹ 25ⅹ 5',2.4,3.05,40.0,25.0,5.0,5.0,0.0,0.67,1.4,4.76,1.42,0.36,5.31,0.87,1.25,0.68,1.32,0.53,1.83,0.77,3.27,1.45,'IS808_Rev',0.25); +INSERT INTO UnequalAngle VALUES(7,'∠ 40 ⅹ 25ⅹ 6',2.82,3.59,40.0,25.0,6.0,5.0,0.0,0.7,1.44,5.5,1.62,0.35,6.1,1.02,1.24,0.67,1.3,0.53,2.15,0.9,3.81,1.72,'IS808_Rev',0.42400000000000002131); +INSERT INTO UnequalAngle VALUES(8,'∠ 45 ⅹ 30ⅹ 3',1.74,2.21,45.0,30.0,3.0,5.0,0.0,0.71,1.44,4.57,1.65,0.41,5.26,0.96,1.44,0.86,1.54,0.66,1.49,0.72,2.7,1.29,'IS808_Rev',0.064000000000000003552); +INSERT INTO UnequalAngle VALUES(9,'∠ 45 ⅹ 30ⅹ 4',2.27,2.89,45.0,30.0,4.0,5.0,0.0,0.74,1.48,5.87,2.1,0.41,6.75,1.22,1.42,0.85,1.53,0.65,1.95,0.93,3.5,1.69,'IS808_Rev',0.15099999999999999644); +INSERT INTO UnequalAngle VALUES(10,'∠ 45 ⅹ 30ⅹ 5',2.79,3.55,45.0,30.0,5.0,5.0,0.0,0.78,1.52,7.08,2.51,0.4,8.11,1.48,1.41,0.84,1.51,0.64,2.38,1.13,4.27,2.08,'IS808_Rev',0.29099999999999997868); +INSERT INTO UnequalAngle VALUES(11,'∠ 45 ⅹ 30ⅹ 6',3.29,4.19,45.0,30.0,6.0,5.0,0.0,0.82,1.56,8.21,2.89,0.4,9.37,1.73,1.4,0.83,1.49,0.64,2.79,1.32,5.0,2.46,'IS808_Rev',0.49599999999999999644); +INSERT INTO UnequalAngle VALUES(12,'∠ 50 ⅹ30ⅹ 3',1.86,2.37,50.0,30.0,3.0,5.5,0.0,0.67,1.64,6.13,1.69,0.35,6.79,1.03,1.61,0.84,1.69,0.66,1.83,0.73,3.28,1.31,'IS808_Rev',0.069000000000000003552); +INSERT INTO UnequalAngle VALUES(13,'∠ 50 ⅹ30ⅹ 4',2.44,3.1,50.0,30.0,4.0,5.5,0.0,0.71,1.69,7.89,2.15,0.34,8.73,1.32,1.59,0.83,1.68,0.65,2.38,0.94,4.26,1.72,'IS808_Rev',0.16200000000000001065); +INSERT INTO UnequalAngle VALUES(14,'∠ 50 ⅹ30ⅹ 5',2.99,3.81,50.0,30.0,5.0,5.5,0.0,0.75,1.73,9.53,2.58,0.34,10.5,1.59,1.58,0.82,1.66,0.65,2.92,1.15,5.19,2.13,'IS808_Rev',0.31200000000000001065); +INSERT INTO UnequalAngle VALUES(15,'∠ 50 ⅹ30ⅹ 6',3.54,4.5,50.0,30.0,6.0,5.5,0.0,0.79,1.77,11.1,2.97,0.33,12.2,1.86,1.57,0.81,1.64,0.64,3.43,1.34,6.09,2.52,'IS808_Rev',0.53200000000000002842); +INSERT INTO UnequalAngle VALUES(16,'∠ 60ⅹ 40ⅹ 5',3.79,4.83,60.0,40.0,5.0,6.0,0.0,0.98,1.97,17.5,6.28,0.41,20.2,3.65,1.91,1.14,2.04,0.87,4.35,2.08,7.83,3.77,'IS808_Rev',0.39500000000000001776); +INSERT INTO UnequalAngle VALUES(17,'∠ 60ⅹ 40ⅹ 6',4.49,5.72,60.0,40.0,6.0,6.0,0.0,1.02,2.01,20.5,7.29,0.41,23.5,4.26,1.89,1.13,2.03,0.86,5.13,2.45,9.21,4.47,'IS808_Rev',0.67600000000000015631); +INSERT INTO UnequalAngle VALUES(18,'∠ 60ⅹ 40ⅹ 8',5.84,7.44,60.0,40.0,8.0,6.0,0.0,1.09,2.08,25.9,9.12,0.4,29.6,5.45,1.87,1.11,1.99,0.86,6.62,3.14,11.8,5.83,'IS808_Rev',1.57); +INSERT INTO UnequalAngle VALUES(19,'∠ 65 ⅹ45ⅹ 5',4.18,5.33,65.0,45.0,5.0,6.0,0.0,1.1,2.09,22.8,9.02,0.44,26.7,5.12,2.07,1.3,2.24,0.98,5.16,2.65,9.33,4.77,'IS808_Rev',0.43700000000000001065); +INSERT INTO UnequalAngle VALUES(20,'∠ 65 ⅹ45ⅹ 6',4.96,6.32,65.0,45.0,6.0,6.0,0.0,1.14,2.13,26.7,10.5,0.44,31.2,5.99,2.06,1.29,2.22,0.97,6.1,3.12,11.0,5.66,'IS808_Rev',0.74800000000000004263); +INSERT INTO UnequalAngle VALUES(21,'∠ 65 ⅹ45ⅹ 8',6.47,8.24,65.0,45.0,8.0,6.0,0.0,1.21,2.2,33.9,13.2,0.43,39.5,7.66,2.03,1.27,2.19,0.96,7.89,4.02,14.1,7.39,'IS808_Rev',1.74); +INSERT INTO UnequalAngle VALUES(22,'∠ 70 ⅹ45ⅹ 5',4.39,5.59,70.0,45.0,5.0,6.5,0.0,1.06,2.29,28.0,9.2,0.39,31.8,5.42,2.24,1.28,2.39,0.98,5.95,2.68,10.7,4.82,'IS808_Rev',0.4580000000000000071); +INSERT INTO UnequalAngle VALUES(23,'∠ 70 ⅹ45ⅹ 6',5.21,6.63,70.0,45.0,6.0,6.5,0.0,1.1,2.33,32.8,10.7,0.39,37.2,6.34,2.23,1.27,2.37,0.98,7.04,3.15,12.6,5.72,'IS808_Rev',0.78399999999999998578); +INSERT INTO UnequalAngle VALUES(24,'∠ 70 ⅹ45ⅹ 8',6.79,8.65,70.0,45.0,8.0,6.5,0.0,1.18,2.41,41.8,13.5,0.38,47.2,8.1,2.2,1.25,2.34,0.97,9.12,4.06,16.3,7.5,'IS808_Rev',1.82); +INSERT INTO UnequalAngle VALUES(25,'∠ 70 ⅹ45ⅹ 10',8.31,10.5,70.0,45.0,10.0,6.5,0.0,1.25,2.49,50.0,16.0,0.37,56.2,9.81,2.17,1.23,2.3,0.96,11.0,4.91,19.7,9.22,'IS808_Rev',3.5); +INSERT INTO UnequalAngle VALUES(26,'∠ 75 ⅹ 50ⅹ 5',4.78,6.09,75.0,50.0,5.0,6.5,0.0,1.18,2.41,35.1,12.7,0.41,40.5,7.32,2.4,1.44,2.58,1.1,6.9,3.32,12.4,5.95,'IS808_Rev',0.5); +INSERT INTO UnequalAngle VALUES(27,'∠ 75 ⅹ 50ⅹ 6',5.68,7.23,75.0,50.0,6.0,6.5,0.0,1.22,2.45,41.2,14.8,0.41,47.5,8.57,2.39,1.43,2.56,1.09,8.17,3.92,14.7,7.07,'IS808_Rev',0.85600000000000004973); +INSERT INTO UnequalAngle VALUES(28,'∠ 75 ⅹ 50ⅹ 8',7.42,9.45,75.0,50.0,8.0,6.5,0.0,1.29,2.53,52.7,18.7,0.41,60.5,10.9,2.36,1.41,2.53,1.08,10.6,5.05,19.0,9.25,'IS808_Rev',1.99); +INSERT INTO UnequalAngle VALUES(29,'∠ 75 ⅹ 50ⅹ 10',9.1,11.5,75.0,50.0,10.0,6.5,0.0,1.37,2.61,63.2,22.3,0.4,72.2,13.3,2.34,1.39,2.5,1.07,12.9,6.13,23.1,11.3,'IS808_Rev',3.83); +INSERT INTO UnequalAngle VALUES(30,'∠ 80ⅹ 50ⅹ 5',4.99,6.36,80.0,50.0,5.0,7.0,0.0,1.14,2.62,42.0,12.9,0.37,47.3,7.68,2.57,1.43,2.73,1.1,7.81,3.34,14.0,5.99,'IS808_Rev',0.52); +INSERT INTO UnequalAngle VALUES(31,'∠ 80ⅹ 50ⅹ 6',5.92,7.55,80.0,50.0,6.0,7.0,0.0,1.18,2.66,49.4,15.1,0.37,55.5,8.99,2.56,1.41,2.71,1.09,9.25,3.95,16.5,7.13,'IS808_Rev',0.89199999999999999289); +INSERT INTO UnequalAngle VALUES(32,'∠ 80ⅹ 50ⅹ 8',7.74,9.87,80.0,50.0,8.0,7.0,0.0,1.26,2.74,63.2,19.1,0.37,70.8,11.5,2.53,1.39,2.68,1.08,12.0,5.09,21.4,9.36,'IS808_Rev',2.08); +INSERT INTO UnequalAngle VALUES(33,'∠ 80ⅹ 50ⅹ 10',9.5,12.1,80.0,50.0,10.0,7.0,0.0,1.33,2.82,76.0,22.7,0.36,84.7,13.9,2.5,1.37,2.65,1.07,14.6,6.18,26.0,11.5,'IS808_Rev',4.0); +INSERT INTO UnequalAngle VALUES(34,'∠ 90 ⅹ 60ⅹ 6',6.88,8.76,90.0,60.0,6.0,7.5,0.0,1.42,2.9,72.8,26.3,0.41,84.0,15.2,2.88,1.73,3.1,1.32,11.9,5.74,21.5,10.2,'IS808_Rev',1.03); +INSERT INTO UnequalAngle VALUES(35,'∠ 90 ⅹ 60ⅹ 8',9.01,11.4,90.0,60.0,8.0,7.5,0.0,1.49,2.98,93.6,33.5,0.41,107.0,19.5,2.86,1.71,3.06,1.3,15.5,7.44,27.9,13.4,'IS808_Rev',2.42); +INSERT INTO UnequalAngle VALUES(36,'∠ 90 ⅹ 60ⅹ 10',11.08,14.1,90.0,60.0,10.0,7.5,0.0,1.57,3.06,113.0,40.1,0.41,129.0,23.6,2.83,1.69,3.03,1.29,19.0,9.05,34.1,16.6,'IS808_Rev',4.66); +INSERT INTO UnequalAngle VALUES(37,'∠ 90 ⅹ 60ⅹ 12',13.09,16.6,90.0,60.0,12.0,7.5,0.0,1.64,3.13,131.0,46.2,0.4,149.0,27.6,2.8,1.66,3.0,1.29,22.3,10.6,39.9,19.7,'IS808_Rev',7.94); +INSERT INTO UnequalAngle VALUES(38,'∠100 ⅹ 65ⅹ 6',7.6,9.68,100.0,65.0,6.0,8.0,0.0,1.5,3.22,100.0,34.0,0.4,114.0,19.8,3.22,1.88,3.44,1.43,14.8,6.8,26.6,12.1,'IS808_Rev',1.14); +INSERT INTO UnequalAngle VALUES(39,'∠100 ⅹ 65ⅹ 8',9.97,12.7,100.0,65.0,8.0,8.0,0.0,1.57,3.3,129.0,43.5,0.4,147.0,25.5,3.19,1.85,3.4,1.42,19.3,8.8,34.6,15.9,'IS808_Rev',2.67); +INSERT INTO UnequalAngle VALUES(40,'∠100 ⅹ 65ⅹ 10',12.28,15.6,100.0,65.0,10.0,8.0,0.0,1.65,3.38,156.0,52.2,0.39,177.0,30.9,3.16,1.83,3.37,1.4,23.6,10.8,42.3,19.7,'IS808_Rev',5.16); +INSERT INTO UnequalAngle VALUES(41,'∠100 ⅹ 75ⅹ 6',8.08,10.3,100.0,75.0,6.0,8.5,0.0,1.82,3.05,105.0,51.2,0.5,128.0,27.6,3.19,2.23,3.54,1.64,15.1,9.0,27.4,16.0,'IS808_Rev',1.21); +INSERT INTO UnequalAngle VALUES(42,'∠100 ⅹ 75ⅹ 8',10.61,13.5,100.0,75.0,8.0,8.5,0.0,1.89,3.13,135.0,65.7,0.5,165.0,35.5,3.17,2.21,3.5,1.62,19.7,11.7,35.8,21.0,'IS808_Rev',2.85); +INSERT INTO UnequalAngle VALUES(43,'∠100 ⅹ 75ⅹ 10',13.07,16.6,100.0,75.0,10.0,8.5,0.0,1.97,3.21,164.0,79.2,0.5,200.0,43.0,3.14,2.18,3.47,1.61,24.2,14.3,43.8,25.9,'IS808_Rev',5.5); +INSERT INTO UnequalAngle VALUES(44,'∠100 ⅹ 75ⅹ 12',15.48,19.7,100.0,75.0,12.0,8.5,0.0,2.04,3.28,191.0,91.7,0.5,232.0,50.4,3.11,2.16,3.43,1.6,28.5,16.8,51.4,30.6,'IS808_Rev',9.38); +INSERT INTO UnequalAngle VALUES(45,'∠ 125 ⅹ 75ⅹ 6',9.27,11.8,125.0,75.0,6.0,9.0,0.0,1.62,4.08,194.0,54.3,0.35,215.0,32.7,4.05,2.14,4.27,1.66,23.1,9.2,41.3,16.4,'IS808_Rev',1.39); +INSERT INTO UnequalAngle VALUES(46,'∠ 125 ⅹ 75ⅹ 8',12.19,15.5,125.0,75.0,8.0,9.0,0.0,1.7,4.17,251.0,69.7,0.35,279.0,42.1,4.03,2.12,4.24,1.65,30.2,12.0,53.9,21.6,'IS808_Rev',3.27); +INSERT INTO UnequalAngle VALUES(47,'∠ 125 ⅹ 75ⅹ 10',15.05,19.1,125.0,75.0,10.0,9.0,0.0,1.78,4.26,306.0,84.1,0.35,339.0,51.0,4.0,2.09,4.21,1.63,37.2,14.7,66.2,26.7,'IS808_Rev',6.33); +INSERT INTO UnequalAngle VALUES(48,'∠125 ⅹ 95 ⅹ 6',10.14,12.9,125.0,95.0,6.0,9.0,4.8,2.24,3.72,205.0,103.0,0.52,254.0,55.0,3.99,2.83,4.44,2.06,23.4,14.3,42.9,25.5,'IS808_Rev',1.54); +INSERT INTO UnequalAngle VALUES(49,'∠125 ⅹ 95ⅹ 8',13.37,17.0,125.0,95.0,8.0,9.0,4.8,2.32,3.8,268.0,134.0,0.52,331.0,71.4,3.97,2.81,4.41,2.05,30.9,18.8,56.4,33.7,'IS808_Rev',3.61); +INSERT INTO UnequalAngle VALUES(50,'∠125 ⅹ 95ⅹ 10',16.54,21.0,125.0,95.0,10.0,9.0,4.8,2.4,3.89,328.0,164.0,0.51,404.0,87.3,3.94,2.79,4.38,2.04,38.1,23.1,69.4,41.7,'IS808_Rev',7.0); +INSERT INTO UnequalAngle VALUES(51,'∠125 ⅹ 95ⅹ 12',19.65,25.0,125.0,95.0,12.0,9.0,4.8,2.48,3.97,384.0,191.0,0.51,473.0,102.0,3.92,2.77,4.35,2.02,45.1,27.3,82.0,49.5,'IS808_Rev',11.9); +INSERT INTO UnequalAngle VALUES(52,'∠150 ⅹ 115ⅹ 8',16.27,20.7,150.0,115.0,8.0,11.0,4.8,2.76,4.48,474.0,244.0,0.52,589.0,128.0,4.78,3.43,5.34,2.49,45.1,27.9,82.4,50.0,'IS808_Rev',4.38); +INSERT INTO UnequalAngle VALUES(53,'∠150 ⅹ 115ⅹ 10',20.14,25.6,150.0,115.0,10.0,11.0,4.8,2.84,4.57,581.0,298.0,0.52,722.0,157.0,4.76,3.41,5.31,2.48,55.8,34.5,101.0,61.9,'IS808_Rev',8.5); +INSERT INTO UnequalAngle VALUES(54,'∠150 ⅹ 115ⅹ 12',23.96,30.5,150.0,115.0,12.0,11.0,4.8,2.92,4.65,684.0,350.0,0.52,849.0,185.0,4.74,3.39,5.28,2.47,66.2,40.8,120.0,73.5,'IS808_Rev',14.5); +INSERT INTO UnequalAngle VALUES(55,'∠150 ⅹ 115ⅹ 16',31.4,40.0,150.0,115.0,16.0,11.0,4.8,3.07,4.81,878.0,446.0,0.52,1080.0,239.0,4.69,3.34,5.21,2.44,86.2,53.0,156.0,96.1,'IS808_Rev',33.9); +INSERT INTO UnequalAngle VALUES(56,'∠200ⅹ 100ⅹ 10',22.93,29.2,200.0,100.0,10.0,12.0,4.8,2.03,6.98,1220.0,214.0,0.26,1300.0,137.0,6.48,2.71,6.68,2.17,94.3,26.9,165.0,48.7,'IS808_Rev',9.66); +INSERT INTO UnequalAngle VALUES(57,'∠200ⅹ 100ⅹ 12',27.29,34.7,200.0,100.0,12.0,12.0,4.8,2.11,7.07,1440.0,251.0,0.26,1530.0,161.0,6.46,2.69,6.65,2.15,112.0,31.9,196.0,58.3,'IS808_Rev',16.5); +INSERT INTO UnequalAngle VALUES(58,'∠200ⅹ 100ⅹ 16',35.84,45.6,200.0,100.0,16.0,12.0,4.8,2.27,7.23,1870.0,319.0,0.25,1980.0,207.0,6.4,2.65,6.59,2.13,146.0,41.3,255.0,77.5,'IS808_Rev',38.7); +INSERT INTO UnequalAngle VALUES(59,'∠200ⅹ 150ⅹ 10',26.92,34.2,200.0,150.0,10.0,13.5,4.8,3.55,6.02,1400.0,688.0,0.51,1720.0,368.0,6.41,4.48,7.1,3.28,100.0,60.2,183.0,107.0,'IS808_Rev',11.3); +INSERT INTO UnequalAngle VALUES(60,'∠200ⅹ 150ⅹ 12',32.07,40.8,200.0,150.0,12.0,13.5,4.8,3.63,6.11,1660.0,812.0,0.51,2040.0,433.0,6.39,4.46,7.07,3.26,119.0,71.4,218.0,127.0,'IS808_Rev',19.4); +INSERT INTO UnequalAngle VALUES(61,'∠200ⅹ 150ⅹ 16',42.18,53.7,200.0,150.0,16.0,13.5,4.8,3.79,6.27,2150.0,1040.0,0.5,2630.0,560.0,6.33,4.41,7.01,3.23,156.0,93.2,285.0,167.0,'IS808_Rev',45.6); +INSERT INTO UnequalAngle VALUES(62,'∠200ⅹ 150ⅹ 20',52.04,66.2,200.0,150.0,20.0,13.5,4.8,3.94,6.42,2610.0,1260.0,0.5,3190.0,682.0,6.28,4.36,6.94,3.21,192.0,114.0,349.0,206.0,'IS808_Rev',88.0); +INSERT INTO UnequalAngle VALUES(63,'∠ 40 ⅹ20ⅹ3',1.37,1.74,40.0,20.0,3.0,4.0,0.0,0.45,1.43,2.9,0.49,0.25,3.04,0.32,1.28,0.53,1.32,0.43,1.11,0.32,1.95,0.59,'IS808_Rev',0.050999999999999996447); +INSERT INTO UnequalAngle VALUES(64,'∠ 40 ⅹ20ⅹ4',1.79,2.27,40.0,20.0,4.0,4.0,0.0,0.49,1.47,3.7,0.62,0.25,3.86,0.41,1.27,0.52,1.3,0.42,1.45,0.41,2.52,0.78,'IS808_Rev',0.11899999999999999467); +INSERT INTO UnequalAngle VALUES(65,'∠ 40 ⅹ20ⅹ5',2.19,2.78,40.0,20.0,5.0,4.0,0.0,0.52,1.51,4.4,0.73,0.24,4.62,0.49,1.25,0.51,1.29,0.42,1.76,0.49,3.05,0.97,'IS808_Rev',0.22900000000000000355); +INSERT INTO UnequalAngle VALUES(66,'∠ 60 ⅹ30ⅹ5',3.4,4.33,60.0,30.0,5.0,6.0,0.0,0.69,2.16,15.9,2.7,0.25,16.8,1.76,1.92,0.79,1.97,0.64,4.14,1.17,7.24,2.21,'IS808_Rev',0.35400000000000000355); +INSERT INTO UnequalAngle VALUES(67,'∠ 60 ⅹ30ⅹ6',4.02,5.12,60.0,30.0,6.0,6.0,0.0,0.73,2.21,18.5,3.11,0.25,19.6,2.06,1.9,0.78,1.96,0.63,4.88,1.37,8.5,2.64,'IS808_Rev',0.60400000000000000355); +INSERT INTO UnequalAngle VALUES(68,'∠ 60 ⅹ40ⅹ7',5.17,6.59,60.0,40.0,7.0,6.0,0.0,1.06,2.05,23.3,8.23,0.4,26.6,4.86,1.88,1.12,2.01,0.86,5.89,2.8,10.5,5.16,'IS808_Rev',1.06); +INSERT INTO UnequalAngle VALUES(69,' ∠65 ⅹ50ⅹ5',4.38,5.58,65.0,50.0,5.0,6.0,0.0,1.26,2.0,23.6,12.2,0.52,29.3,6.48,2.06,1.48,2.29,1.08,5.25,3.27,9.53,5.85,'IS808_Rev',0.4580000000000000071); +INSERT INTO UnequalAngle VALUES(70,'∠65 ⅹ50ⅹ6',5.19,6.62,65.0,50.0,6.0,6.0,0.0,1.3,2.04,27.6,14.2,0.52,34.3,7.59,2.04,1.47,2.28,1.07,6.2,3.85,11.2,6.93,'IS808_Rev',0.78399999999999998578); +INSERT INTO UnequalAngle VALUES(71,'∠65 ⅹ50ⅹ7',6.0,7.64,65.0,50.0,7.0,6.0,0.0,1.34,2.08,31.5,16.2,0.52,39.0,8.67,2.03,1.45,2.26,1.07,7.13,4.42,12.9,7.99,'IS808_Rev',1.23); +INSERT INTO UnequalAngle VALUES(72,'∠65 ⅹ50ⅹ8',6.78,8.64,65.0,50.0,8.0,6.0,0.0,1.38,2.12,35.2,18.0,0.52,43.4,9.72,2.02,1.44,2.24,1.06,8.03,4.97,14.5,9.03,'IS808_Rev',1.82); +INSERT INTO UnequalAngle VALUES(73,'∠ 70ⅹ 50ⅹ5',4.57,5.83,70.0,50.0,5.0,6.0,0.0,1.22,2.21,29.0,12.5,0.46,34.5,6.92,2.23,1.46,2.43,1.09,6.05,3.3,10.9,5.9,'IS808_Rev',0.47900000000000000355); +INSERT INTO UnequalAngle VALUES(74,'∠ 70ⅹ 50ⅹ6',5.43,6.92,70.0,50.0,6.0,6.0,0.0,1.26,2.25,34.0,14.5,0.46,40.4,8.11,2.22,1.45,2.42,1.08,7.16,3.89,12.9,7.0,'IS808_Rev',0.82); +INSERT INTO UnequalAngle VALUES(75,'∠ 70ⅹ 50ⅹ7',6.27,7.99,70.0,50.0,7.0,6.0,0.0,1.3,2.29,38.8,16.5,0.46,46.0,9.26,2.2,1.44,2.4,1.08,8.23,4.46,14.8,8.08,'IS808_Rev',1.29); +INSERT INTO UnequalAngle VALUES(76,'∠ 70ⅹ 50ⅹ8',7.09,9.04,70.0,50.0,8.0,6.0,0.0,1.33,2.33,43.4,18.4,0.46,51.4,10.3,2.19,1.43,2.38,1.07,9.28,5.01,16.7,9.14,'IS808_Rev',1.91); +INSERT INTO UnequalAngle VALUES(77,'∠75 ⅹ50ⅹ7',6.57,8.37,75.0,50.0,7.0,7.0,0.0,1.26,2.49,47.1,16.8,0.41,54.2,9.8,2.37,1.42,2.54,1.08,9.41,4.49,16.9,8.17,'IS808_Rev',1.34); +INSERT INTO UnequalAngle VALUES(78,'∠80 ⅹ40ⅹ5',4.6,5.86,80.0,40.0,5.0,7.0,0.0,0.86,2.82,39.0,6.74,0.26,41.4,4.36,2.58,1.07,2.66,0.86,7.53,2.14,13.1,3.94,'IS808_Rev',0.47900000000000000355); +INSERT INTO UnequalAngle VALUES(79,'∠80 ⅹ40ⅹ6',5.45,6.95,80.0,40.0,6.0,7.0,0.0,0.89,2.86,45.7,7.84,0.25,48.5,5.09,2.57,1.06,2.64,0.86,8.9,2.52,15.5,4.7,'IS808_Rev',0.82); +INSERT INTO UnequalAngle VALUES(80,'∠80 ⅹ40ⅹ7',6.29,8.02,80.0,40.0,7.0,7.0,0.0,0.93,2.91,52.2,8.87,0.25,55.3,5.8,2.55,1.05,2.63,0.85,10.2,2.89,17.8,5.47,'IS808_Rev',1.29); +INSERT INTO UnequalAngle VALUES(81,'∠80 ⅹ40ⅹ8',7.12,9.07,80.0,40.0,8.0,7.0,0.0,0.97,2.95,58.4,9.84,0.25,61.7,6.5,2.54,1.04,2.61,0.85,11.5,3.25,20.1,6.24,'IS808_Rev',1.91); +INSERT INTO UnequalAngle VALUES(82,'∠ 80ⅹ 60ⅹ6',6.42,8.18,80.0,60.0,6.0,8.0,0.0,1.5,2.48,52.6,25.5,0.5,64.3,13.8,2.54,1.77,2.8,1.3,9.5,5.7,17.3,10.1,'IS808_Rev',0.96400000000000005684); +INSERT INTO UnequalAngle VALUES(83,'∠ 80ⅹ 60ⅹ7',7.42,9.45,80.0,60.0,7.0,8.0,0.0,1.54,2.52,60.1,29.1,0.5,73.4,15.8,2.52,1.75,2.79,1.29,11.0,6.5,19.9,11.7,'IS808_Rev',1.52); +INSERT INTO UnequalAngle VALUES(84,'∠ 80ⅹ 60ⅹ8',8.4,10.7,80.0,60.0,8.0,8.0,0.0,1.57,2.56,67.4,32.5,0.5,82.2,17.7,2.51,1.74,2.77,1.29,12.4,7.3,22.4,13.3,'IS808_Rev',2.25); +INSERT INTO UnequalAngle VALUES(85,'∠ 90ⅹ 65 ⅹ 6',7.13,9.08,90.0,65.0,6.0,8.0,0.0,1.57,2.81,74.8,33.1,0.47,89.7,18.3,2.87,1.91,3.14,1.42,12.1,6.7,21.9,12.0,'IS808_Rev',1.07); +INSERT INTO UnequalAngle VALUES(86,'∠ 90ⅹ 65 ⅹ7',8.24,10.5,90.0,65.0,7.0,8.0,0.0,1.61,2.85,85.7,37.8,0.47,102.0,20.9,2.86,1.9,3.13,1.41,13.9,7.7,25.2,13.9,'IS808_Rev',1.69); +INSERT INTO UnequalAngle VALUES(87,'∠ 90ⅹ 65 ⅹ8',9.34,11.9,90.0,65.0,8.0,8.0,0.0,1.65,2.89,96.3,42.3,0.47,115.0,23.5,2.84,1.89,3.11,1.4,15.8,8.7,28.5,15.7,'IS808_Rev',2.5); +INSERT INTO UnequalAngle VALUES(88,'∠ 90ⅹ 65 ⅹ10',11.49,14.6,90.0,65.0,10.0,8.0,0.0,1.73,2.97,116.0,50.7,0.47,138.0,28.4,2.82,1.86,3.08,1.39,19.3,10.6,34.8,19.3,'IS808_Rev',4.83); +INSERT INTO UnequalAngle VALUES(89,'∠100 ⅹ50 ⅹ 6',6.92,8.81,100.0,50.0,6.0,9.0,0.0,1.06,3.51,91.9,15.9,0.26,97.5,10.3,3.23,1.34,3.33,1.08,14.2,4.0,24.8,7.4,'IS808_Rev',1.03); +INSERT INTO UnequalAngle VALUES(90,'∠100 ⅹ50 ⅹ 7',7.99,10.1,100.0,50.0,7.0,9.0,0.0,1.1,3.56,105.0,18.1,0.26,111.0,11.7,3.21,1.33,3.31,1.07,16.3,4.6,28.6,8.6,'IS808_Rev',1.63); +INSERT INTO UnequalAngle VALUES(91,'∠100 ⅹ50 ⅹ 8',9.05,11.5,100.0,50.0,8.0,9.0,0.0,1.14,3.6,118.0,20.2,0.25,125.0,13.1,3.2,1.32,3.29,1.07,18.5,5.2,32.2,9.8,'IS808_Rev',2.42); +INSERT INTO UnequalAngle VALUES(92,'∠100 ⅹ50 ⅹ10',11.13,14.1,100.0,50.0,10.0,9.0,0.0,1.21,3.68,142.0,24.0,0.25,150.0,15.9,3.17,1.3,3.26,1.06,22.6,6.3,39.3,12.2,'IS808_Rev',4.66); +INSERT INTO UnequalAngle VALUES(93,'∠100 ⅹ65 ⅹ7',8.85,11.2,100.0,65.0,7.0,10.0,0.0,1.53,3.25,115.0,38.9,0.4,131.0,22.8,3.2,1.86,3.41,1.42,17.1,7.8,30.7,14.1,'IS808_Rev',1.8); +INSERT INTO UnequalAngle VALUES(94,'∠120ⅹ80 ⅹ8',12.26,15.6,120.0,80.0,8.0,11.0,0.0,1.89,3.85,230.0,83.2,0.41,265.0,48.1,3.84,2.31,4.12,1.75,28.3,13.6,51.0,24.4,'IS808_Rev',3.27); +INSERT INTO UnequalAngle VALUES(95,'∠120ⅹ80 ⅹ10',15.12,19.2,120.0,80.0,10.0,11.0,0.0,1.96,3.94,280.0,100.0,0.41,322.0,58.3,3.81,2.28,4.09,1.74,34.8,16.6,62.6,30.1,'IS808_Rev',6.33); +INSERT INTO UnequalAngle VALUES(96,'∠120ⅹ80 ⅹ12',17.91,22.8,120.0,80.0,12.0,11.0,0.0,2.04,4.02,327.0,116.0,0.41,375.0,68.1,3.79,2.26,4.06,1.73,41.0,19.6,73.7,35.7,'IS808_Rev',10.8); +INSERT INTO UnequalAngle VALUES(97,'∠125ⅹ75 ⅹ12',17.91,22.8,125.0,75.0,12.0,11.0,0.0,1.85,4.32,358.0,97.5,0.34,396.0,59.8,3.97,2.07,4.17,1.62,43.9,17.3,78.1,31.8,'IS808_Rev',10.8); +INSERT INTO UnequalAngle VALUES(98,'∠135ⅹ65 ⅹ8',12.18,15.5,135.0,65.0,8.0,11.0,4.8,1.35,4.79,292.0,45.5,0.24,308.0,29.7,4.34,1.71,4.46,1.38,33.6,8.8,59.0,16.4,'IS808_Rev',3.27); +INSERT INTO UnequalAngle VALUES(99,'∠135ⅹ65 ⅹ10',15.04,19.1,135.0,65.0,10.0,11.0,4.8,1.43,4.88,357.0,55.0,0.24,376.0,36.1,4.32,1.69,4.43,1.37,41.4,10.8,72.5,20.5,'IS808_Rev',6.33); +INSERT INTO UnequalAngle VALUES(100,'∠135ⅹ65 ⅹ12',17.84,22.7,135.0,65.0,12.0,11.0,4.8,1.51,4.97,418.0,63.9,0.24,440.0,42.3,4.29,1.68,4.4,1.36,49.0,12.8,85.4,24.6,'IS808_Rev',10.8); +INSERT INTO UnequalAngle VALUES(101,'∠150ⅹ75 ⅹ9',15.39,19.6,150.0,75.0,9.0,11.0,4.8,1.58,5.28,457.0,78.8,0.26,485.0,50.7,4.83,2.01,4.98,1.61,47.1,13.3,82.8,24.5,'IS808_Rev',5.24); +INSERT INTO UnequalAngle VALUES(102,'∠150ⅹ75 ⅹ15',24.85,31.6,150.0,75.0,15.0,11.0,4.8,1.81,5.53,715.0,120.0,0.25,755.0,79.1,4.75,1.95,4.89,1.58,75.5,21.1,131.0,40.7,'IS808_Rev',23.6); +INSERT INTO UnequalAngle VALUES(103,'∠150ⅹ90ⅹ10',18.22,23.2,150.0,90.0,10.0,12.0,4.8,2.04,5.0,536.0,147.0,0.35,594.0,89.1,4.81,2.52,5.06,1.96,53.6,21.2,96.2,38.4,'IS808_Rev',7.66); +INSERT INTO UnequalAngle VALUES(104,'∠150ⅹ90ⅹ12',21.64,27.5,150.0,90.0,12.0,12.0,4.8,2.12,5.09,630.0,172.0,0.34,698.0,104.0,4.78,2.5,5.03,1.95,63.6,25.0,113.0,45.8,'IS808_Rev',13.1); +INSERT INTO UnequalAngle VALUES(105,'∠150ⅹ90ⅹ15',26.66,33.9,150.0,90.0,15.0,12.0,4.8,2.24,5.21,764.0,206.0,0.34,843.0,126.0,4.74,2.47,4.98,1.93,78.0,30.6,139.0,56.7,'IS808_Rev',25.3); +INSERT INTO UnequalAngle VALUES(106,'∠200ⅹ100ⅹ15',33.86,43.1,200.0,100.0,15.0,15.0,4.8,2.23,7.17,1770.0,303.0,0.25,1870.0,196.0,6.41,2.65,6.6,2.13,138.0,39.0,241.0,72.9,'IS808_Rev',32.0); +INSERT INTO UnequalAngle VALUES(107,'∠200ⅹ150ⅹ15',39.75,50.6,200.0,150.0,15.0,15.0,4.8,3.75,6.22,2030.0,988.0,0.5,2490.0,530.0,6.34,4.42,7.02,3.24,147.0,87.8,268.0,157.0,'IS808_Rev',37.6); +INSERT INTO UnequalAngle VALUES(108,'∠200ⅹ150ⅹ18',47.21,60.1,200.0,150.0,18.0,15.0,4.8,3.86,6.34,2390.0,1150.0,0.5,2920.0,623.0,6.3,4.38,6.97,3.22,175.0,103.0,317.0,187.0,'IS808_Rev',64.5); +CREATE TABLE IF NOT EXISTS "EqualAngle" ( + "Id" INTEGER NOT NULL, + "Designation" VARCHAR(50), + "Mass" REAL(10 , 2), + "Area" REAL(10 , 2), + "a" REAL(10 , 2), + "b" REAL(10 , 2), + "t" REAL(10 , 2), + "R1" REAL(10 , 2), + "R2" REAL(10 , 2), + "Cz" REAL(10 , 2), + "Cy" REAL(10 , 2), + "Iz" REAL(10 , 2), + "Iy" REAL(10 , 2), + "Alpha" REAL(10 , 2), + "Iu(max)" REAL(10 , 2), + "Iv(min)" REAL(10 , 2), + "rz" REAL(10 , 2), + "ry" REAL(10 , 2), + "ru(max)" REAL(10 , 2), + "rv(min)" REAL(10 , 2), + "Zz" REAL(10 , 2), + "Zy" REAL(10 , 2), + "Zpz" REAL(10 , 2), + "Zpy" REAL(10 , 2), + "Source" VARCHAR(50), + "It" REAL(10 , 2), + PRIMARY KEY("Id") +); +INSERT INTO EqualAngle VALUES(1,'∠ 20ⅹ 20ⅹ 3',0.9,1.14,20.0,20.0,3.0,4.0,0.0,0.6,0.6,0.4,0.4,0.79,0.64,0.17,0.59,0.59,0.75,0.39,0.29,0.29,0.52,0.53,'IS808_Rev',0.033000000000000007105); +INSERT INTO EqualAngle VALUES(2,'∠ 20ⅹ 20ⅹ 4',1.16,1.47,20.0,20.0,4.0,4.0,0.0,0.64,0.64,0.5,0.5,0.79,0.79,0.22,0.58,0.58,0.73,0.39,0.37,0.37,0.66,0.67,'IS808_Rev',0.075999999999999996447); +INSERT INTO EqualAngle VALUES(3,'∠ 25ⅹ 25ⅹ 3',1.14,1.45,25.0,25.0,3.0,4.5,0.0,0.73,0.73,0.83,0.83,0.79,1.3,0.35,0.75,0.75,0.95,0.49,0.46,0.46,0.83,0.84,'IS808_Rev',0.042000000000000001776); +INSERT INTO EqualAngle VALUES(4,'∠ 25ⅹ 25ⅹ 4',1.48,1.88,25.0,25.0,4.0,4.5,0.0,0.76,0.76,1.04,1.04,0.79,1.63,0.44,0.74,0.74,0.93,0.48,0.6,0.6,1.07,1.09,'IS808_Rev',0.098000000000000024868); +INSERT INTO EqualAngle VALUES(5,'∠ 25ⅹ 25ⅹ 5',1.8,2.29,25.0,25.0,5.0,4.5,0.0,0.8,0.8,1.23,1.23,0.79,1.92,0.54,0.73,0.73,0.92,0.48,0.72,0.72,1.3,1.31,'IS808_Rev',0.18700000000000001065); +INSERT INTO EqualAngle VALUES(6,'∠ 30ⅹ 30ⅹ 3',1.38,1.76,30.0,30.0,3.0,5.0,0.0,0.85,0.85,1.47,1.47,0.79,2.32,0.62,0.91,0.91,1.15,0.59,0.68,0.68,1.22,1.23,'IS808_Rev',0.050999999999999996447); +INSERT INTO EqualAngle VALUES(7,'∠ 30ⅹ 30ⅹ 4',1.8,2.29,30.0,30.0,4.0,5.0,0.0,0.89,0.89,1.86,1.86,0.79,2.94,0.78,0.9,0.9,1.13,0.58,0.88,0.88,1.58,1.6,'IS808_Rev',0.11899999999999999467); +INSERT INTO EqualAngle VALUES(8,'∠ 30ⅹ 30ⅹ 5',2.2,2.8,30.0,30.0,5.0,5.0,0.0,0.93,0.93,2.22,2.22,0.79,3.49,0.95,0.89,0.89,1.12,0.58,1.07,1.07,1.92,1.94,'IS808_Rev',0.22900000000000000355); +INSERT INTO EqualAngle VALUES(9,'∠ 35ⅹ 35ⅹ 3',1.62,2.06,35.0,35.0,3.0,5.0,0.0,0.97,0.97,2.38,2.38,0.79,3.77,0.99,1.07,1.07,1.35,0.69,0.94,0.94,1.69,1.7,'IS808_Rev',0.06); +INSERT INTO EqualAngle VALUES(10,'∠ 35ⅹ 35ⅹ 4',2.11,2.69,35.0,35.0,4.0,5.0,0.0,1.01,1.01,3.04,3.04,0.79,4.81,1.27,1.06,1.06,1.34,0.69,1.22,1.22,2.19,2.21,'IS808_Rev',0.14); +INSERT INTO EqualAngle VALUES(11,'∠ 35ⅹ 35ⅹ 5',2.59,3.3,35.0,35.0,5.0,5.0,0.0,1.05,1.05,3.65,3.65,0.79,5.76,1.54,1.05,1.05,1.32,0.68,1.49,1.49,2.68,2.69,'IS808_Rev',0.27); +INSERT INTO EqualAngle VALUES(12,'∠ 35ⅹ 35ⅹ 6',3.06,3.89,35.0,35.0,6.0,5.0,0.0,1.09,1.09,4.2,4.2,0.79,6.61,1.8,1.04,1.04,1.3,0.68,1.74,1.74,3.14,3.15,'IS808_Rev',0.46); +INSERT INTO EqualAngle VALUES(13,'∠ 40ⅹ 40ⅹ 3',1.86,2.37,40.0,40.0,3.0,5.5,0.0,1.09,1.09,3.61,3.61,0.79,5.72,1.51,1.23,1.23,1.55,0.8,1.24,1.24,2.22,2.24,'IS808_Rev',0.069000000000000003552); +INSERT INTO EqualAngle VALUES(14,'∠ 40ⅹ 40ⅹ 4',2.44,3.1,40.0,40.0,4.0,5.5,0.0,1.13,1.13,4.63,4.63,0.79,7.34,1.93,1.22,1.22,1.54,0.79,1.62,1.62,2.9,2.92,'IS808_Rev',0.16200000000000001065); +INSERT INTO EqualAngle VALUES(15,'∠ 40ⅹ 40ⅹ 5',2.99,3.81,40.0,40.0,5.0,5.5,0.0,1.17,1.17,5.58,5.58,0.79,8.83,2.33,1.21,1.21,1.52,0.78,1.97,1.97,3.55,3.57,'IS808_Rev',0.31200000000000001065); +INSERT INTO EqualAngle VALUES(16,'∠ 40ⅹ 40ⅹ 6',3.54,4.5,40.0,40.0,6.0,5.5,0.0,1.21,1.21,6.46,6.46,0.79,10.2,2.73,1.2,1.2,1.5,0.78,2.32,2.32,4.17,4.19,'IS808_Rev',0.53200000000000002842); +INSERT INTO EqualAngle VALUES(17,'∠ 45ⅹ 45ⅹ 3',2.1,2.67,45.0,45.0,3.0,5.5,0.0,1.22,1.22,5.2,5.2,0.79,8.2,2.17,1.39,1.39,1.76,0.9,1.58,1.58,2.84,2.86,'IS808_Rev',0.078000000000000015987); +INSERT INTO EqualAngle VALUES(18,'∠ 45ⅹ 45ⅹ 4',2.75,3.5,45.0,45.0,4.0,5.5,0.0,1.26,1.26,6.7,6.7,0.79,10.6,2.78,1.38,1.38,1.74,0.89,2.07,2.07,3.71,3.73,'IS808_Rev',0.1830000000000000071); +INSERT INTO EqualAngle VALUES(19,'∠ 45ⅹ 45ⅹ 5',3.39,4.31,45.0,45.0,5.0,5.5,0.0,1.3,1.3,8.1,8.1,0.79,12.8,3.37,1.37,1.37,1.72,0.88,2.53,2.53,4.55,4.57,'IS808_Rev',0.35400000000000000355); +INSERT INTO EqualAngle VALUES(20,'∠ 45ⅹ 45ⅹ 6',4.01,5.1,45.0,45.0,6.0,5.5,0.0,1.34,1.34,9.42,9.42,0.79,14.9,3.94,1.36,1.36,1.71,0.88,2.98,2.98,5.36,5.38,'IS808_Rev',0.60400000000000000355); +INSERT INTO EqualAngle VALUES(21,'∠ 50ⅹ 50ⅹ 3',2.34,2.99,50.0,50.0,3.0,6.0,0.0,1.34,1.34,7.21,7.21,0.79,11.4,3.01,1.55,1.55,1.96,1.0,1.97,1.97,3.53,3.55,'IS808_Rev',0.086999999999999992894); +INSERT INTO EqualAngle VALUES(22,'∠ 50ⅹ 50ⅹ 4',3.08,3.92,50.0,50.0,4.0,6.0,0.0,1.38,1.38,9.32,9.32,0.79,14.8,3.86,1.54,1.54,1.94,0.99,2.57,2.57,4.62,4.64,'IS808_Rev',0.20400000000000000355); +INSERT INTO EqualAngle VALUES(23,'∠ 50ⅹ 50ⅹ 5',3.79,4.83,50.0,50.0,5.0,6.0,0.0,1.42,1.42,11.3,11.3,0.79,17.9,4.69,1.53,1.53,1.93,0.99,3.16,3.16,5.67,5.7,'IS808_Rev',0.39500000000000001776); +INSERT INTO EqualAngle VALUES(24,'∠ 50ⅹ 50ⅹ 6',4.49,5.72,50.0,50.0,6.0,6.0,0.0,1.46,1.46,13.2,13.2,0.79,20.8,5.48,1.52,1.52,1.91,0.98,3.72,3.72,6.69,6.71,'IS808_Rev',0.67600000000000015631); +INSERT INTO EqualAngle VALUES(25,'∠ 55ⅹ 55ⅹ 4',3.4,4.33,55.0,55.0,4.0,6.5,0.0,1.5,1.5,12.5,12.5,0.79,19.9,5.2,1.7,1.7,2.14,1.1,3.14,3.14,5.63,5.66,'IS808_Rev',0.22600000000000002309); +INSERT INTO EqualAngle VALUES(26,'∠ 55ⅹ 55ⅹ 5',4.19,5.34,55.0,55.0,5.0,6.5,0.0,1.54,1.54,15.2,15.2,0.79,24.2,6.31,1.69,1.69,2.13,1.09,3.85,3.85,6.92,6.95,'IS808_Rev',0.43700000000000001065); +INSERT INTO EqualAngle VALUES(27,'∠ 55ⅹ 55ⅹ 6',4.97,6.33,55.0,55.0,6.0,6.5,0.0,1.58,1.58,17.8,17.8,0.79,28.2,7.39,1.68,1.68,2.11,1.08,4.55,4.55,8.17,8.2,'IS808_Rev',0.74800000000000004263); +INSERT INTO EqualAngle VALUES(28,'∠ 55ⅹ 55ⅹ 8',6.48,8.25,55.0,55.0,8.0,6.5,0.0,1.66,1.66,22.5,22.5,0.79,35.6,9.48,1.65,1.65,2.08,1.07,5.87,5.87,10.5,10.6,'IS808_Rev',1.74); +INSERT INTO EqualAngle VALUES(29,'∠ 60ⅹ 60ⅹ 4',3.71,4.73,60.0,60.0,4.0,6.5,0.0,1.63,1.63,16.4,16.4,0.79,26.0,6.8,1.86,1.86,2.35,1.2,3.76,3.76,6.74,6.77,'IS808_Rev',0.24699999999999993072); +INSERT INTO EqualAngle VALUES(30,'∠ 60ⅹ 60ⅹ 5',4.58,5.84,60.0,60.0,5.0,6.5,0.0,1.67,1.67,20.0,20.0,0.79,31.7,8.26,1.85,1.85,2.33,1.19,4.62,4.62,8.3,8.32,'IS808_Rev',0.47900000000000000355); +INSERT INTO EqualAngle VALUES(31,'∠ 60ⅹ 60ⅹ 6',5.44,6.93,60.0,60.0,6.0,6.5,0.0,1.71,1.71,23.4,23.4,0.79,37.1,9.69,1.84,1.84,2.31,1.18,5.46,5.46,9.81,9.84,'IS808_Rev',0.82); +INSERT INTO EqualAngle VALUES(32,'∠ 60ⅹ 60ⅹ 8',7.1,9.05,60.0,60.0,8.0,6.5,0.0,1.78,1.78,29.8,29.8,0.79,47.1,12.4,1.81,1.81,2.28,1.17,7.06,7.06,12.7,12.7,'IS808_Rev',1.91); +INSERT INTO EqualAngle VALUES(33,'∠ 65ⅹ 65ⅹ 4',4.03,5.13,65.0,65.0,4.0,6.5,0.0,1.75,1.75,21.0,21.0,0.79,33.4,8.69,2.02,2.02,2.55,1.3,4.43,4.43,7.95,7.98,'IS808_Rev',0.26800000000000001598); +INSERT INTO EqualAngle VALUES(34,'∠ 65ⅹ 65ⅹ 5',4.98,6.34,65.0,65.0,5.0,6.5,0.0,1.79,1.79,25.7,25.7,0.79,40.8,10.6,2.01,2.01,2.54,1.29,5.45,5.45,9.8,9.83,'IS808_Rev',0.52); +INSERT INTO EqualAngle VALUES(35,'∠ 65ⅹ 65ⅹ 6',5.91,7.53,65.0,65.0,6.0,6.5,0.0,1.83,1.83,30.1,30.1,0.79,47.8,12.4,2.0,2.0,2.52,1.28,6.45,6.45,11.5,11.6,'IS808_Rev',0.89199999999999999289); +INSERT INTO EqualAngle VALUES(36,'∠ 65ⅹ 65ⅹ 8',7.73,9.85,65.0,65.0,8.0,6.5,0.0,1.91,1.91,38.4,38.4,0.79,60.8,16.0,1.97,1.97,2.48,1.27,8.36,8.36,15.0,15.0,'IS808_Rev',2.08); +INSERT INTO EqualAngle VALUES(37,'∠ 70ⅹ 70ⅹ 5',5.38,6.86,70.0,70.0,5.0,7.0,0.0,1.92,1.92,32.3,32.3,0.79,51.3,13.3,2.17,2.17,2.74,1.39,6.36,6.36,11.4,11.4,'IS808_Rev',0.56200000000000009947); +INSERT INTO EqualAngle VALUES(38,'∠ 70ⅹ 70ⅹ 6',6.39,8.15,70.0,70.0,6.0,7.0,0.0,1.96,1.96,38.0,38.0,0.79,60.3,15.6,2.16,2.16,2.72,1.39,7.53,7.53,13.5,13.5,'IS808_Rev',0.96400000000000005684); +INSERT INTO EqualAngle VALUES(39,'∠ 70ⅹ 70ⅹ 8',8.37,10.6,70.0,70.0,8.0,7.0,0.0,2.03,2.03,48.5,48.5,0.79,76.9,20.1,2.13,2.13,2.69,1.37,9.77,9.77,17.5,17.6,'IS808_Rev',2.25); +INSERT INTO EqualAngle VALUES(40,'∠ 70ⅹ 70ⅹ 10',10.29,13.1,70.0,70.0,10.0,7.0,0.0,2.11,2.11,58.3,58.3,0.79,92.1,24.4,2.11,2.11,2.65,1.37,11.9,11.9,21.4,21.5,'IS808_Rev',4.33); +INSERT INTO EqualAngle VALUES(41,'∠ 75ⅹ 75ⅹ 5',5.77,7.36,75.0,75.0,5.0,7.0,0.0,2.04,2.04,40.0,40.0,0.79,63.6,16.5,2.33,2.33,2.94,1.5,7.3,7.3,13.2,13.2,'IS808_Rev',0.60400000000000000355); +INSERT INTO EqualAngle VALUES(42,'∠ 75ⅹ 75ⅹ 6',6.86,8.75,75.0,75.0,6.0,7.0,0.0,2.08,2.08,47.1,47.1,0.79,74.8,19.4,2.32,2.32,2.92,1.49,8.7,8.7,15.6,15.6,'IS808_Rev',1.03); +INSERT INTO EqualAngle VALUES(43,'∠ 75ⅹ 75ⅹ 8',9.0,11.4,75.0,75.0,8.0,7.0,0.0,2.16,2.16,60.3,60.3,0.79,95.7,24.9,2.29,2.29,2.89,1.47,11.3,11.3,20.3,20.4,'IS808_Rev',2.42); +INSERT INTO EqualAngle VALUES(44,'∠ 75ⅹ 75ⅹ 10',11.07,14.1,75.0,75.0,10.0,7.0,0.0,2.23,2.23,72.6,72.6,0.79,114.0,30.3,2.27,2.27,2.85,1.47,13.8,13.8,24.8,24.9,'IS808_Rev',4.66); +INSERT INTO EqualAngle VALUES(45,'∠ 80ⅹ 80ⅹ 6',7.36,9.38,80.0,80.0,6.0,8.0,0.0,2.2,2.2,57.6,57.6,0.79,91.4,23.7,2.48,2.48,3.12,1.59,9.9,9.9,17.8,17.9,'IS808_Rev',1.1); +INSERT INTO EqualAngle VALUES(46,'∠ 80ⅹ 80ⅹ 8',9.65,12.3,80.0,80.0,8.0,8.0,0.0,2.28,2.28,74.0,74.0,0.79,117.0,30.5,2.45,2.45,3.09,1.58,12.9,12.9,23.3,23.3,'IS808_Rev',2.59); +INSERT INTO EqualAngle VALUES(47,'∠ 80ⅹ 80ⅹ 10',11.88,15.1,80.0,80.0,10.0,8.0,0.0,2.36,2.36,89.2,89.2,0.79,141.0,37.1,2.43,2.43,3.05,1.57,15.8,15.8,28.4,28.5,'IS808_Rev',5.0); +INSERT INTO EqualAngle VALUES(48,'∠ 80ⅹ 80ⅹ 12',14.05,17.9,80.0,80.0,12.0,8.0,0.0,2.43,2.43,103.0,103.0,0.79,163.0,43.5,2.4,2.4,3.02,1.56,18.5,18.5,33.4,33.5,'IS808_Rev',8.52); +INSERT INTO EqualAngle VALUES(49,'∠ 90ⅹ 90ⅹ 6',8.32,10.6,90.0,90.0,6.0,8.5,0.0,2.45,2.45,83.0,83.0,0.79,131.0,34.2,2.8,2.8,3.53,1.8,12.7,12.7,22.8,22.8,'IS808_Rev',1.25); +INSERT INTO EqualAngle VALUES(50,'∠ 90ⅹ 90ⅹ 8',10.92,13.9,90.0,90.0,8.0,8.5,0.0,2.53,2.53,107.0,107.0,0.79,170.0,44.1,2.77,2.77,3.5,1.78,16.5,16.5,29.7,29.8,'IS808_Rev',2.93); +INSERT INTO EqualAngle VALUES(51,'∠ 90ⅹ 90ⅹ 10',13.47,17.1,90.0,90.0,10.0,8.5,0.0,2.6,2.6,129.0,129.0,0.79,205.0,53.6,2.75,2.75,3.46,1.77,20.2,20.2,36.4,36.5,'IS808_Rev',5.66); +INSERT INTO EqualAngle VALUES(52,'∠ 90ⅹ 90ⅹ 12',15.95,20.3,90.0,90.0,12.0,8.5,0.0,2.68,2.68,150.0,150.0,0.79,238.0,62.8,2.72,2.72,3.42,1.76,23.8,23.8,42.9,43.0,'IS808_Rev',9.67); +INSERT INTO EqualAngle VALUES(53,'∠ 100 ⅹ 100ⅹ 6',9.26,11.8,100.0,100.0,6.0,8.5,0.0,2.7,2.7,115.0,115.0,0.79,182.0,47.2,3.12,3.12,3.94,2.0,15.7,15.7,28.3,28.3,'IS808_Rev',1.39); +INSERT INTO EqualAngle VALUES(54,'∠ 100 ⅹ 100ⅹ 8',12.18,15.5,100.0,100.0,8.0,8.5,0.0,2.78,2.78,148.0,148.0,0.79,236.0,61.0,3.1,3.1,3.9,1.98,20.6,20.6,37.0,37.1,'IS808_Rev',3.27); +INSERT INTO EqualAngle VALUES(55,'∠ 100 ⅹ 100ⅹ 10',15.04,19.1,100.0,100.0,10.0,8.5,0.0,2.85,2.85,180.0,180.0,0.79,286.0,74.3,3.07,3.07,3.87,1.97,25.3,25.3,45.4,45.5,'IS808_Rev',6.33); +INSERT INTO EqualAngle VALUES(56,'∠ 100 ⅹ 100ⅹ 12',17.83,22.7,100.0,100.0,12.0,8.5,0.0,2.93,2.93,210.0,210.0,0.79,333.0,87.2,3.04,3.04,3.83,1.96,29.8,29.8,53.6,53.7,'IS808_Rev',10.8); +INSERT INTO EqualAngle VALUES(57,'∠ 110 ⅹ 110ⅹ 8',13.4,17.0,110.0,110.0,8.0,10.0,4.8,3.0,3.0,196.0,196.0,0.79,312.0,80.7,3.39,3.39,4.28,2.17,24.6,24.6,44.6,44.7,'IS808_Rev',3.61); +INSERT INTO EqualAngle VALUES(58,'∠ 110 ⅹ 110ⅹ 10',16.58,21.1,110.0,110.0,10.0,10.0,4.8,3.09,3.09,240.0,240.0,0.79,381.0,98.6,3.37,3.37,4.25,2.16,30.4,30.4,54.9,55.0,'IS808_Rev',7.0); +INSERT INTO EqualAngle VALUES(59,'∠ 110 ⅹ 110ⅹ 12',19.68,25.0,110.0,110.0,12.0,10.0,4.8,3.17,3.17,281.0,281.0,0.79,446.0,116.0,3.35,3.35,4.22,2.15,35.9,35.9,64.9,65.1,'IS808_Rev',11.9); +INSERT INTO EqualAngle VALUES(60,'∠ 110 ⅹ 110ⅹ 16',25.71,32.7,110.0,110.0,16.0,10.0,4.8,3.32,3.32,357.0,357.0,0.79,565.0,149.0,3.3,3.3,4.15,2.14,46.5,46.5,84.1,84.2,'IS808_Rev',27.8); +INSERT INTO EqualAngle VALUES(61,'∠ 130ⅹ 130ⅹ 8',15.92,20.2,130.0,130.0,8.0,10.0,4.8,3.5,3.5,330.0,330.0,0.79,526.0,135.0,4.04,4.04,5.1,2.58,34.8,34.8,63.0,63.1,'IS808_Rev',4.3); +INSERT INTO EqualAngle VALUES(62,'∠ 130ⅹ130ⅹ 10',19.72,25.1,130.0,130.0,10.0,10.0,4.8,3.59,3.59,405.0,405.0,0.79,644.0,165.0,4.02,4.02,5.07,2.57,43.1,43.1,77.8,77.9,'IS808_Rev',8.33); +INSERT INTO EqualAngle VALUES(63,'∠ 130ⅹ130ⅹ 12',23.45,29.8,130.0,130.0,12.0,10.0,4.8,3.67,3.67,476.0,476.0,0.79,757.0,195.0,3.99,3.99,5.04,2.56,51.0,51.0,92.2,92.3,'IS808_Rev',14.2); +INSERT INTO EqualAngle VALUES(64,'∠ 130ⅹ130ⅹ 16',30.74,39.1,130.0,130.0,16.0,10.0,4.8,3.82,3.82,609.0,609.0,0.79,966.0,252.0,3.94,3.94,4.97,2.54,66.3,66.3,119.0,120.0,'IS808_Rev',33.3); +INSERT INTO EqualAngle VALUES(65,'∠150ⅹ 150ⅹ 10',22.93,29.2,150.0,150.0,10.0,12.0,4.8,4.08,4.08,633.0,633.0,0.79,1000.0,259.0,4.66,4.66,5.87,2.98,58.0,58.0,104.0,104.0,'IS808_Rev',9.66); +INSERT INTO EqualAngle VALUES(66,'∠150ⅹ 150ⅹ 12',27.29,34.7,150.0,150.0,12.0,12.0,4.8,4.16,4.16,746.0,746.0,0.79,1180.0,305.0,4.63,4.63,5.84,2.96,68.8,68.8,124.0,124.0,'IS808_Rev',16.5); +INSERT INTO EqualAngle VALUES(67,'∠150ⅹ 150ⅹ 16',35.84,45.6,150.0,150.0,16.0,12.0,4.8,4.31,4.31,958.0,958.0,0.79,1520.0,394.0,4.58,4.58,5.78,2.94,89.7,89.7,162.0,162.0,'IS808_Rev',38.7); +INSERT INTO EqualAngle VALUES(68,'∠150ⅹ 150ⅹ 20',44.12,56.2,150.0,150.0,20.0,12.0,4.8,4.46,4.46,1150.0,1150.0,0.79,1830.0,480.0,4.53,4.53,5.71,2.92,109.0,109.0,198.0,198.0,'IS808_Rev',74.6); +INSERT INTO EqualAngle VALUES(69,'∠200 ⅹ 200ⅹ 12',36.85,46.9,200.0,200.0,12.0,15.0,4.8,5.39,5.39,1820.0,1820.0,0.79,2900.0,746.0,6.24,6.24,7.87,3.99,125.0,125.0,225.0,225.0,'IS808_Rev',22.3); +INSERT INTO EqualAngle VALUES(70,'∠200 ⅹ 200ⅹ 16',48.53,61.8,200.0,200.0,16.0,15.0,4.8,5.56,5.56,2360.0,2360.0,0.79,3760.0,967.0,6.19,6.19,7.8,3.96,163.0,163.0,295.0,295.0,'IS808_Rev',52.4); +INSERT INTO EqualAngle VALUES(71,'∠200 ⅹ 200ⅹ 20',59.96,76.3,200.0,200.0,20.0,15.0,4.8,5.71,5.71,2870.0,2870.0,0.79,4560.0,1180.0,6.13,6.13,7.73,3.93,201.0,201.0,362.0,363.0,'IS808_Rev',101.0); +INSERT INTO EqualAngle VALUES(72,'∠200 ⅹ 200ⅹ 25',73.9,94.1,200.0,200.0,25.0,15.0,4.8,5.9,5.9,3470.0,3470.0,0.79,5500.0,1430.0,6.07,6.07,7.65,3.91,246.0,246.0,443.0,444.0,'IS808_Rev',195.0); +INSERT INTO EqualAngle VALUES(73,'∠50 ⅹ 50ⅹ 7',5.17,6.59,50.0,50.0,7.0,6.0,0.0,1.5,1.5,14.9,14.9,0.79,23.6,6.27,1.51,1.51,1.89,0.98,4.26,4.26,7.67,7.7,'IS808_Rev',1.06); +INSERT INTO EqualAngle VALUES(74,'∠50 ⅹ 50ⅹ 8',5.84,7.44,50.0,50.0,8.0,6.0,0.0,1.54,1.54,16.6,16.6,0.79,26.2,7.03,1.49,1.49,1.88,0.97,4.79,4.79,8.62,8.65,'IS808_Rev',1.57); +INSERT INTO EqualAngle VALUES(75,'∠55 ⅹ 55 x10',7.92,10.0,55.0,55.0,10.0,6.5,0.0,1.73,1.73,26.8,26.8,0.79,42.1,11.5,1.63,1.63,2.04,1.07,7.11,7.11,12.8,12.8,'IS808_Rev',3.33); +INSERT INTO EqualAngle VALUES(76,'∠60 ⅹ 60 ⅹ 10',8.71,11.0,60.0,60.0,10.0,6.5,0.0,1.86,1.86,35.5,35.5,0.79,55.9,15.1,1.79,1.79,2.25,1.17,8.57,8.57,15.4,15.4,'IS808_Rev',3.66); +INSERT INTO EqualAngle VALUES(77,'∠65 ⅹ 65ⅹ 10',9.49,12.0,65.0,65.0,10.0,6.5,0.0,1.98,1.98,45.9,45.9,0.79,72.5,19.4,1.95,1.95,2.45,1.27,10.1,10.1,18.3,18.3,'IS808_Rev',4.0); +INSERT INTO EqualAngle VALUES(78,'∠ 70 ⅹ 70ⅹ 7',7.39,9.42,70.0,70.0,7.0,7.0,0.0,2.0,2.0,43.4,43.4,0.79,68.8,17.9,2.15,2.15,2.7,1.38,8.66,8.66,15.5,15.6,'IS808_Rev',1.52); +INSERT INTO EqualAngle VALUES(79,'∠ 100 ⅹ 100ⅹ 7',10.73,13.6,100.0,100.0,7.0,8.5,0.0,2.74,2.74,132.0,132.0,0.79,210.0,54.2,3.11,3.11,3.92,1.99,18.2,18.2,32.7,32.7,'IS808_Rev',2.2); +INSERT INTO EqualAngle VALUES(80,'∠ 100 ⅹ 100ⅹ 15',21.91,27.9,100.0,100.0,15.0,8.5,0.0,3.04,3.04,252.0,252.0,0.79,398.0,106.0,3.01,3.01,3.78,1.95,36.2,36.2,65.3,65.4,'IS808_Rev',20.8); +INSERT INTO EqualAngle VALUES(81,'∠ 120 ⅹ 120ⅹ 8',14.66,18.6,120.0,120.0,8.0,10.0,4.8,3.25,3.25,258.0,258.0,0.79,410.0,105.0,3.72,3.72,4.69,2.38,29.5,29.5,53.4,53.5,'IS808_Rev',3.95); +INSERT INTO EqualAngle VALUES(82,'∠ 120 ⅹ 120ⅹ 10',18.15,23.1,120.0,120.0,10.0,10.0,4.8,3.34,3.34,315.0,315.0,0.79,501.0,129.0,3.69,3.69,4.66,2.36,36.4,36.4,65.9,66.0,'IS808_Rev',7.66); +INSERT INTO EqualAngle VALUES(83,'∠ 120 ⅹ 120ⅹ 12',21.57,27.4,120.0,120.0,12.0,10.0,4.8,3.42,3.42,370.0,370.0,0.79,588.0,152.0,3.67,3.67,4.63,2.35,43.1,43.1,78.0,78.1,'IS808_Rev',13.1); +INSERT INTO EqualAngle VALUES(84,'∠ 120 ⅹ 120ⅹ 15',26.58,33.8,120.0,120.0,15.0,10.0,4.8,3.53,3.53,447.0,447.0,0.79,709.0,185.0,3.64,3.64,4.58,2.34,52.8,52.8,95.5,95.6,'IS808_Rev',25.3); +INSERT INTO EqualAngle VALUES(85,'∠ 130 ⅹ 130ⅹ 9',17.82,22.7,130.0,130.0,9.0,10.0,4.8,3.55,3.55,368.0,368.0,0.79,586.0,150.0,4.03,4.03,5.08,2.57,39.0,39.0,70.5,70.6,'IS808_Rev',6.09); +INSERT INTO EqualAngle VALUES(86,'∠150 ⅹ 150ⅹ 15',33.72,42.9,150.0,150.0,15.0,12.0,4.8,4.28,4.28,907.0,907.0,0.79,1440.0,372.0,4.6,4.6,5.79,2.95,84.6,84.6,152.0,152.0,'IS808_Rev',32.0); +INSERT INTO EqualAngle VALUES(87,'∠150 ⅹ 150ⅹ 18',40.01,50.9,150.0,150.0,18.0,12.0,4.8,4.39,4.39,1050.0,1050.0,0.79,1680.0,437.0,4.56,4.56,5.74,2.93,99.8,99.8,180.0,180.0,'IS808_Rev',54.8); +INSERT INTO EqualAngle VALUES(88,'∠180 ⅹ 180ⅹ 15',41.09,52.3,180.0,180.0,15.0,18.0,4.8,5.0,5.0,1610.0,1610.0,0.79,2550.0,663.0,5.55,5.55,6.99,3.56,123.0,123.0,223.0,223.0,'IS808_Rev',38.8); +INSERT INTO EqualAngle VALUES(89,'∠180 ⅹ 180ⅹ 18',48.79,62.1,180.0,180.0,18.0,18.0,4.8,5.12,5.12,1880.0,1880.0,0.79,2990.0,778.0,5.51,5.51,6.94,3.54,146.0,146.0,264.0,264.0,'IS808_Rev',66.4); +INSERT INTO EqualAngle VALUES(90,'∠180 ⅹ 180ⅹ 20',53.85,68.6,180.0,180.0,20.0,18.0,4.8,5.2,5.2,2060.0,2060.0,0.79,3270.0,853.0,5.49,5.49,6.91,3.53,161.0,161.0,290.0,291.0,'IS808_Rev',90.6); +INSERT INTO EqualAngle VALUES(91,'∠200 ⅹ 200ⅹ 24',71.31,90.8,200.0,200.0,24.0,18.0,4.8,5.85,5.85,3350.0,3350.0,0.79,5320.0,1390.0,6.08,6.08,7.65,3.91,237.0,237.0,427.0,428.0,'IS808_Rev',173.0); +CREATE TABLE IF NOT EXISTS "Columns" ( + "Id" INTEGER, + "Designation" VARCHAR(50), + "Mass" REAL(10 , 2), + "Area" REAL(10 , 2), + "D" REAL(10 , 2), + "B" REAL(10 , 2), + "tw" REAL(10 , 2), + "T" INTEGER(10 , 2), + "FlangeSlope" INTEGER DEFAULT (null), + "R1" REAL(10 , 2), + "R2" REAL(10 , 2), + "Iz" REAL(10 , 2), + "Iy" REAL(10 , 2), + "rz" REAL(10 , 2), + "ry" REAL(10 , 2), + "Zz" REAL(10 , 2), + "Zy" REAL(10 , 2), + "Zpz" REAL(10 , 2), + "Zpy" REAL(10 , 2), + "It" REAL(10 , 2), + "Iw" REAL(10 , 2), + "Source" VARCHAR(100), + "Type" BVARCHAR(100), + PRIMARY KEY("Id") +); +INSERT INTO Columns VALUES(1,'HB 150',27.06,34.4,150.0,150.0,5.4,9,94,8.0,4.0,1450.0,431.0,6.49,3.53,194.0,57.5,215.0,92.7,10.1,25100.0,'IS808_Rev',NULL); +INSERT INTO Columns VALUES(2,'HB 150*',30.15,38.4,150.0,150.0,8.4,9,94,8.0,4.0,1510.0,435.0,6.27,3.36,201.0,58.0,228.0,94.7,12.6,25100.0,'IS808_Rev',NULL); +INSERT INTO Columns VALUES(3,'HB 150*',33.66,42.9,150.0,150.0,11.8,9,94,8.0,4.0,1570.0,439.0,6.06,3.2,210.0,58.6,243.0,97.6,17.4,25100.0,'IS808_Rev',NULL); +INSERT INTO Columns VALUES(4,'HB 200',37.31,47.5,200.0,200.0,6.1,9,94,9.0,4.5,3600.0,967.0,8.71,4.51,360.0,96.7,397.0,159.0,14.9,109000.0,'IS808_Rev',NULL); +INSERT INTO Columns VALUES(5,'HB 200*',39.73,50.6,200.0,200.0,7.8,9,94,9.0,4.5,3690.0,971.0,8.54,4.38,369.0,97.1,411.0,160.0,16.6,109000.0,'IS808_Rev',NULL); +INSERT INTO Columns VALUES(6,'HB 225',43.12,54.9,225.0,225.0,6.5,9.1,94,10.0,5.0,5280.0,1350.0,9.8,4.96,469.0,120.0,515.0,200.0,18.3,201000.0,'IS808_Rev',NULL); +INSERT INTO Columns VALUES(7,'HB 225*',46.52,59.2,225.0,225.0,8.6,9.1,94,10.0,5.0,5430.0,1360.0,9.57,4.79,483.0,121.0,538.0,203.0,20.8,201000.0,'IS808_Rev',NULL); +INSERT INTO Columns VALUES(8,'HB 250',50.98,64.9,250.0,250.0,6.9,9.7,94,10.0,5.0,7730.0,1960.0,10.9,5.49,619.0,156.0,678.0,262.0,24.5,364000.0,'IS808_Rev',NULL); +INSERT INTO Columns VALUES(9,'HB 250*',54.41,69.3,250.0,250.0,8.8,9.7,94,10.0,5.0,7930.0,1970.0,10.6,5.33,634.0,157.0,704.0,264.0,27.2,364000.0,'IS808_Rev',NULL); +INSERT INTO Columns VALUES(10,'HB 300',58.74,74.8,300.0,250.0,7.6,10.6,94,11.0,5.5,12500.0,2190.0,12.9,5.41,836.0,175.0,921.0,291.0,32.4,577000.0,'IS808_Rev',NULL); +INSERT INTO Columns VALUES(11,'HB 300*',62.67,79.8,300.0,250.0,9.4,10.6,94,11.0,5.5,12800.0,2200.0,12.6,5.25,858.0,176.0,956.0,294.0,36.2,577000.0,'IS808_Rev',NULL); +INSERT INTO Columns VALUES(12,'HB 350',67.42,85.9,350.0,250.0,8.3,11.6,94,12.0,6.0,19100.0,2450.0,14.9,5.34,1090.0,196.0,1210.0,324.0,42.8,864000.0,'IS808_Rev',NULL); +INSERT INTO Columns VALUES(13,'HB 350*',72.03,91.7,350.0,250.0,10.1,11.6,94,12.0,6.0,19600.0,2460.0,14.6,5.17,1120.0,196.0,1260.0,328.0,48.3,864000.0,'IS808_Rev',NULL); +INSERT INTO Columns VALUES(14,'HB 400',77.43,98.6,400.0,250.0,9.1,12.7,94,14.0,7.0,28000.0,2720.0,16.8,5.25,1400.0,218.0,1560.0,360.0,57.8,1240000.0,'IS808_Rev',NULL); +INSERT INTO Columns VALUES(15,'HB 400*',81.83,104.0,400.0,250.0,10.6,12.7,94,14.0,7.0,28700.0,2730.0,16.6,5.12,1430.0,218.0,1610.0,364.0,63.9,1240000.0,'IS808_Rev',NULL); +INSERT INTO Columns VALUES(16,'HB 450',87.22,111.0,450.0,250.0,9.8,13.7,94,15.0,7.5,39200.0,2980.0,18.7,5.18,1740.0,238.0,1950.0,394.0,73.5,1690000.0,'IS808_Rev',NULL); +INSERT INTO Columns VALUES(17,'HB 450*',92.19,117.0,450.0,250.0,11.3,13.7,94,15.0,7.5,40100.0,2990.0,18.4,5.04,1780.0,239.0,2020.0,398.0,81.5,1690000.0,'IS808_Rev',NULL); +INSERT INTO Columns VALUES(18,'PBP 200 X 43.85',43.85,55.8,200.0,205.0,9.3,9.3,90,10.0,0.0,3990.0,1330.0,8.46,4.89,399.0,130.0,447.0,199.0,17.9,121000.0,'IS808_Rev',NULL); +INSERT INTO Columns VALUES(19,'PBP 200 X 53.49',53.49,68.1,204.0,207.0,11.3,11.3,90,10.0,0.0,4970.0,1670.0,8.54,4.96,487.0,161.0,551.0,248.0,31.9,155000.0,'IS808_Rev',NULL); +INSERT INTO Columns VALUES(20,'PBP 220 X 57.28',57.28,72.9,210.0,225.0,11.0,11,90,18.0,0.0,5730.0,2090.0,8.87,5.36,546.0,186.0,614.0,286.0,37.6,206000.0,'IS808_Rev',NULL); +INSERT INTO Columns VALUES(21,'PBP 260 X 75.01',75.01,95.5,249.0,265.0,12.0,12,90,24.0,0.0,10600.0,3730.0,10.5,6.25,854.0,281.0,958.0,435.0,64.3,522000.0,'IS808_Rev',NULL); +INSERT INTO Columns VALUES(22,'PBP 260 X 87.3',87.3,111.0,253.0,267.0,14.0,14,90,24.0,0.0,12500.0,4450.0,10.6,6.33,993.0,333.0,1120.0,516.0,96.6,634000.0,'IS808_Rev',NULL); +INSERT INTO Columns VALUES(23,'PBP 300 X 76.92',76.92,97.9,299.0,306.0,10.8,10.8,90,15.0,0.0,16000.0,5160.0,12.7,7.26,1070.0,337.0,1180.0,515.0,43.5,1070000.0,'IS808_Rev',NULL); +INSERT INTO Columns VALUES(24,'PBP 300 X 88.46',88.46,112.0,302.0,308.0,12.4,12.4,90,15.0,0.0,18500.0,6040.0,12.8,7.32,1220.0,392.0,1370.0,600.0,65.0,1260000.0,'IS808_Rev',NULL); +INSERT INTO Columns VALUES(25,'PBP 300 X 95',95.0,121.0,304.0,309.0,13.3,13.3,90,15.0,0.0,20000.0,6540.0,12.8,7.36,1320.0,423.0,1470.0,649.0,79.8,1380000.0,'IS808_Rev',NULL); +INSERT INTO Columns VALUES(26,'PBP 300 X 109.54',109.54,139.0,308.0,311.0,15.3,15.3,90,15.0,0.0,23400.0,7680.0,12.9,7.42,1520.0,494.0,1710.0,758.0,120.0,1640000.0,'IS808_Rev',NULL); +INSERT INTO Columns VALUES(27,'PBP 300 X 124.2',124.2,158.0,312.0,313.0,17.3,17.3,90,15.0,0.0,26900.0,8850.0,13.0,7.48,1720.0,565.0,1950.0,870.0,173.0,1910000.0,'IS808_Rev',NULL); +INSERT INTO Columns VALUES(28,'PBP 300 X 150.01',150.01,191.0,319.0,316.0,20.8,20.8,90,15.0,0.0,33200.0,10900.0,13.2,7.57,2080.0,693.0,2380.0,1070.0,300.0,2430000.0,'IS808_Rev',NULL); +INSERT INTO Columns VALUES(29,'PBP 300 X 180.12',180.12,229.0,327.0,320.0,24.8,24.8,90,15.0,0.0,41000.0,13500.0,13.3,7.69,2500.0,849.0,2900.0,1310.0,510.0,3090000.0,'IS808_Rev',NULL); +INSERT INTO Columns VALUES(30,'PBP 300 X 184.12',184.12,234.0,328.0,321.0,25.3,25.3,90,15.0,0.0,42000.0,13900.0,13.3,7.72,2560.0,871.0,2970.0,1350.0,542.0,3190000.0,'IS808_Rev',NULL); +INSERT INTO Columns VALUES(31,'PBP 300 X 222.58',222.58,283.0,338.0,326.0,30.3,30.3,90,15.0,0.0,52500.0,17500.0,13.6,7.87,3100.0,1070.0,3640.0,1670.0,936.0,4140000.0,'IS808_Rev',NULL); +INSERT INTO Columns VALUES(32,'PBP 320 X 88.48',88.48,112.0,303.0,304.0,12.0,12,90,27.0,0.0,18700.0,5630.0,12.8,7.07,1230.0,370.0,1370.0,572.0,78.8,1180000.0,'IS808_Rev',NULL); +INSERT INTO Columns VALUES(33,'PBP 320 X 102.84',102.84,131.0,307.0,306.0,14.0,14,90,27.0,0.0,22000.0,6700.0,12.9,7.15,1430.0,438.0,1610.0,677.0,117.0,1430000.0,'IS808_Rev',NULL); +INSERT INTO Columns VALUES(34,'PBP 320 X 117.33',117.33,149.0,311.0,308.0,16.0,16,90,27.0,0.0,25400.0,7810.0,13.0,7.23,1630.0,507.0,1840.0,785.0,167.0,1690000.0,'IS808_Rev',NULL); +INSERT INTO Columns VALUES(35,'PBP 320 X 146.69',146.69,186.0,319.0,312.0,20.0,20,90,27.0,0.0,32600.0,10100.0,13.2,7.37,2040.0,651.0,2330.0,1010.0,309.0,2260000.0,'IS808_Rev',NULL); +INSERT INTO Columns VALUES(36,'PBP 320 X 184.1',184.1,234.0,329.0,317.0,25.0,25,90,27.0,0.0,42200.0,13300.0,13.4,7.54,2560.0,841.0,2970.0,1310.0,586.0,3060000.0,'IS808_Rev',NULL); +INSERT INTO Columns VALUES(37,'PBP 360 X 152.2',152.2,193.0,356.0,376.0,18.0,17.9,90,15.0,0.0,43800.0,15800.0,15.0,9.0,2460.0,844.0,2760.0,1290.0,225.0,4530000.0,'IS808_Rev',NULL); +INSERT INTO Columns VALUES(38,'PBP 360 X 174.2',174.2,221.0,361.0,379.0,20.0,20.4,90,15.0,0.0,50900.0,18500.0,15.1,9.1,2820.0,978.0,3180.0,1500.0,325.0,5360000.0,'IS808_Rev',NULL); +INSERT INTO Columns VALUES(39,'PBP 360 X 178.4',178.4,227.0,362.0,379.0,21.0,20.9,90,15.0,0.0,52200.0,18900.0,15.2,9.1,2880.0,1000.0,3260.0,1530.0,357.0,5510000.0,'IS808_Rev',NULL); +INSERT INTO Columns VALUES(40,'PBP 400 X 122.4',122.4,155.0,348.0,390.0,14.0,14,90,15.0,0.0,34700.0,13800.0,14.9,9.4,1990.0,710.0,2210.0,1080.0,111.0,3860000.0,'IS808_Rev',NULL); +INSERT INTO Columns VALUES(41,'PBP 400 X 140.2',140.2,178.0,352.0,392.0,16.0,16,90,15.0,0.0,40200.0,16000.0,15.0,9.5,2280.0,820.0,2540.0,1250.0,165.0,4530000.0,'IS808_Rev',NULL); +INSERT INTO Columns VALUES(42,'PBP 400 X 158.1',158.1,201.0,356.0,394.0,18.0,18,90,15.0,0.0,45900.0,18300.0,15.1,9.6,2570.0,932.0,2880.0,1420.0,234.0,5240000.0,'IS808_Rev',NULL); +INSERT INTO Columns VALUES(43,'PBP 400 X 176.1',176.1,224.0,360.0,396.0,20.0,20,90,15.0,0.0,51700.0,20700.0,15.2,9.6,2870.0,1040.0,3230.0,1600.0,321.0,5980000.0,'IS808_Rev',NULL); +INSERT INTO Columns VALUES(44,'PBP 400 X 194.3',194.3,247.0,364.0,398.0,22.0,22,90,15.0,0.0,57600.0,23100.0,15.3,9.7,3160.0,1160.0,3580.0,1780.0,428.0,6750000.0,'IS808_Rev',NULL); +INSERT INTO Columns VALUES(45,'PBP 400 X 212.5',212.5,270.0,368.0,400.0,24.0,24,90,15.0,0.0,63800.0,25600.0,15.4,9.7,3460.0,1280.0,3940.0,1960.0,556.0,7570000.0,'IS808_Rev',NULL); +INSERT INTO Columns VALUES(46,'PBP 400 X 230.9',230.9,294.0,372.0,402.0,26.0,26,90,15.0,0.0,70100.0,28200.0,15.4,9.8,3770.0,1400.0,4310.0,2150.0,707.0,8420000.0,'IS808_Rev',NULL); +INSERT INTO Columns VALUES(47,'SC 100',19.97,25.4,100.0,100.0,6.0,10,98,12.0,6.0,434.0,136.0,4.13,2.31,87.0,27.2,101.0,45.2,10.6,3370.0,'IS808_Rev',NULL); +INSERT INTO Columns VALUES(48,'SC 120',26.22,33.4,120.0,120.0,6.5,11,98,12.0,6.0,840.0,255.0,5.02,2.77,140.0,42.6,161.0,70.8,16.5,9400.0,'IS808_Rev',NULL); +INSERT INTO Columns VALUES(49,'SC 140',33.25,42.3,140.0,140.0,7.0,12,98,12.0,6.0,1470.0,437.0,5.9,3.21,210.0,62.5,240.0,104.0,24.6,22400.0,'IS808_Rev',NULL); +INSERT INTO Columns VALUES(50,'SC 150*',36.93,47.0,152.0,152.0,7.9,11.9,98,11.7,3.0,1920.0,554.0,6.39,3.43,253.0,72.9,288.0,122.0,27.6,34100.0,'IS808_Old',NULL); +INSERT INTO Columns VALUES(51,'SC 160',41.85,53.3,160.0,160.0,8.0,13,98,15.0,7.5,2410.0,694.0,6.74,3.61,302.0,86.8,345.0,146.0,38.3,47900.0,'IS808_Rev',NULL); +INSERT INTO Columns VALUES(52,'SC 180',50.48,64.3,180.0,180.0,8.5,14,98,15.0,7.5,3730.0,1050.0,7.62,4.05,414.0,117.0,471.0,197.0,52.8,93700.0,'IS808_Rev',NULL); +INSERT INTO Columns VALUES(53,'SC 200',60.24,76.7,200.0,200.0,9.0,15,98,18.0,9.0,5520.0,1520.0,8.49,4.46,552.0,152.0,627.0,259.0,74.9,171000.0,'IS808_Rev',NULL); +INSERT INTO Columns VALUES(54,'SC 220',70.41,89.6,220.0,220.0,9.5,16,98,18.0,9.0,7860.0,2150.0,9.37,4.9,715.0,196.0,809.0,333.0,98.4,295000.0,'IS808_Rev',NULL); +INSERT INTO Columns VALUES(55,'SC 250',85.54,108.0,250.0,250.0,10.0,17,98,23.0,11.5,12400.0,3250.0,10.6,5.47,995.0,260.0,1120.0,448.0,143.0,600000.0,'IS808_Rev',NULL); +INSERT INTO Columns VALUES(56,'UC 152 x 152 x 23',23.0,29.2,152.4,152.2,5.8,6.8,90,7.6,0.0,1250.0,400.0,6.54,3.7,164.0,52.5,182.0,80.2,NULL,NULL,'IS808_Rev',NULL); +INSERT INTO Columns VALUES(57,'UC 152 x 152 x 30',30.0,38.3,157.6,152.9,6.5,9.4,90,7.6,0.0,1748.0,560.0,6.76,3.83,222.0,73.3,248.0,112.0,NULL,NULL,'IS808_Rev',NULL); +INSERT INTO Columns VALUES(58,'UC 152 x 152 x 37',37.0,47.1,161.8,154.4,8.0,11.5,90,7.6,0.0,2210.0,706.0,6.85,3.87,273.0,91.5,309.0,140.0,NULL,NULL,'IS808_Rev',NULL); +INSERT INTO Columns VALUES(59,'UC 203 x 203 x 46',46.1,58.7,203.2,203.6,7.2,11,90,10.2,0.0,4568.0,1548.0,8.82,5.13,450.0,152.0,497.0,231.0,NULL,NULL,'IS808_Rev',NULL); +INSERT INTO Columns VALUES(60,'UC 203 x 203 x 52',52.0,66.3,206.2,204.3,7.9,12.5,90,10.2,0.0,5259.0,1777.0,8.91,5.18,510.0,174.0,567.0,264.0,NULL,NULL,'IS808_Rev',NULL); +INSERT INTO Columns VALUES(61,'UC 203 x 203 x 60',60.0,76.4,209.6,205.8,9.4,14.2,90,10.2,0.0,6125.0,2064.0,8.95,5.2,584.0,201.0,656.0,305.0,NULL,NULL,'IS808_Rev',NULL); +INSERT INTO Columns VALUES(62,'UC 203 x 203 x 71',71.0,90.4,215.8,206.4,10.0,17.3,90,10.2,0.0,7618.0,2537.0,9.18,5.3,706.0,246.0,799.0,374.0,NULL,NULL,'IS808_Rev',NULL); +INSERT INTO Columns VALUES(63,'UC 203 x 203 x 86',86.1,109.6,222.2,209.1,12.7,20.5,90,10.2,0.0,9449.0,3127.0,9.28,5.34,850.0,299.0,977.0,456.0,NULL,NULL,'IS808_Rev',NULL); +INSERT INTO Columns VALUES(64,'UC 254 x 254 x 107',107.1,136.4,266.7,258.8,12.8,20.5,90,12.7,0.0,17510.0,5927.0,11.3,6.59,1313.0,458.0,1484.0,697.0,NULL,NULL,'IS808_Rev',NULL); +INSERT INTO Columns VALUES(65,'UC 254 x 254 x 132',132.0,168.1,276.3,261.3,15.3,25.3,90,12.7,0.0,22529.0,7531.0,11.6,6.69,1631.0,576.0,1869.0,878.0,NULL,NULL,'IS808_Rev',NULL); +INSERT INTO Columns VALUES(66,'UC 254 x 254 x 167',167.1,212.9,289.1,265.2,19.2,31.7,90,12.7,0.0,29998.0,9869.0,11.9,6.81,2075.0,744.0,2424.0,1137.0,NULL,NULL,'IS808_Rev',NULL); +INSERT INTO Columns VALUES(67,'UC 254 x 254 x 73',73.1,93.1,254.1,254.6,8.6,14.2,90,12.7,0.0,11407.0,3907.0,11.1,6.48,898.0,307.0,992.0,465.0,NULL,NULL,'IS808_Rev',NULL); +INSERT INTO Columns VALUES(68,'UC 254 x 254 x 89',88.9,113.3,260.3,256.3,10.3,17.3,90,12.7,0.0,14268.0,4857.0,11.2,6.55,1096.0,379.0,1224.0,575.0,NULL,NULL,'IS808_Rev',NULL); +INSERT INTO Columns VALUES(69,'UC 305 x 305 x 118',117.9,150.2,314.5,307.4,12.0,18.7,90,15.2,0.0,27672.0,9058.0,13.6,7.77,1760.0,589.0,1958.0,895.0,NULL,NULL,'IS808_Rev',NULL); +INSERT INTO Columns VALUES(70,'UC 305 x 305 x 137',136.9,174.4,320.5,309.2,13.8,21.7,90,15.2,0.0,32814.0,10698.0,13.7,7.83,2048.0,692.0,2297.0,1053.0,NULL,NULL,'IS808_Rev',NULL); +INSERT INTO Columns VALUES(71,'UC 305 x 305 x 158',158.1,201.4,327.1,311.2,15.8,25,90,15.2,0.0,38747.0,12568.0,13.9,7.9,2369.0,808.0,2680.0,1230.0,NULL,NULL,'IS808_Rev',NULL); +INSERT INTO Columns VALUES(72,'UC 305 x 305 x 198',198.1,252.4,339.9,314.5,19.1,31.4,90,15.2,0.0,50904.0,16298.0,14.2,8.04,2995.0,1036.0,3440.0,1581.0,NULL,NULL,'IS808_Rev',NULL); +INSERT INTO Columns VALUES(73,'UC 305 x 305 x 240',240.0,305.8,352.5,318.4,23.0,37.7,90,15.2,0.0,64203.0,20313.0,14.5,8.15,3643.0,1276.0,4247.0,1951.0,NULL,NULL,'IS808_Rev',NULL); +INSERT INTO Columns VALUES(74,'UC 305 x 305 x 283',282.9,360.4,365.3,322.2,26.8,44.1,90,15.2,0.0,78872.0,24633.0,14.8,8.27,4318.0,1529.0,5105.0,2342.0,NULL,NULL,'IS808_Rev',NULL); +INSERT INTO Columns VALUES(75,'UC 305 x 305 x 97',96.9,123.4,307.9,305.3,9.9,15.4,90,15.2,0.0,22249.0,7307.0,13.4,7.69,1445.0,479.0,1592.0,726.0,NULL,NULL,'IS808_Rev',NULL); +INSERT INTO Columns VALUES(76,'UC 356 x 368 x 129',129.0,164.3,355.6,368.6,10.4,17.5,90,15.2,0.0,40246.0,14610.0,15.6,9.43,2264.0,793.0,2479.0,1199.0,NULL,NULL,'IS808_Rev',NULL); +INSERT INTO Columns VALUES(77,'UC 356 x 368 x 153',152.9,194.8,362.0,370.5,12.3,20.7,90,15.2,0.0,48589.0,17552.0,15.8,9.49,2684.0,947.0,2965.0,1435.0,NULL,NULL,'IS808_Rev',NULL); +INSERT INTO Columns VALUES(78,'UC 356 x 368 x 177',177.0,225.5,368.2,372.6,14.4,23.8,90,15.2,0.0,57118.0,20528.0,15.9,9.54,3103.0,1102.0,3455.0,1671.0,NULL,NULL,'IS808_Rev',NULL); +INSERT INTO Columns VALUES(79,'UC 356 x 368 x 202',201.9,257.2,374.6,374.7,16.5,27,90,15.2,0.0,66261.0,23687.0,16.1,9.6,3538.0,1264.0,3972.0,1920.0,NULL,NULL,'IS808_Rev',NULL); +INSERT INTO Columns VALUES(80,'UC 356 x 406 x 235',235.1,299.4,381.0,394.8,18.4,30.2,90,15.2,0.0,79085.0,30992.0,16.3,10.2,4151.0,1570.0,4687.0,2383.0,NULL,NULL,'IS808_Rev',NULL); +INSERT INTO Columns VALUES(81,'UC 356 x 406 x 287',287.1,365.7,393.6,399.0,22.6,36.5,90,15.2,0.0,99875.0,38676.0,16.5,10.3,5075.0,1939.0,5812.0,2949.0,NULL,NULL,'IS808_Rev',NULL); +INSERT INTO Columns VALUES(82,'UC 356 x 406 x 340',339.9,433.0,406.4,403.0,26.6,42.9,90,15.2,0.0,122543.0,46851.0,16.8,10.4,6031.0,2325.0,6999.0,3544.0,NULL,NULL,'IS808_Rev',NULL); +INSERT INTO Columns VALUES(83,'UC 356 x 406 x 393',393.0,500.6,419.0,407.0,30.6,49.2,90,15.2,0.0,146618.0,55365.0,17.1,10.5,6998.0,2721.0,8222.0,4154.0,NULL,NULL,'IS808_Rev',NULL); +INSERT INTO Columns VALUES(84,'UC 356 x 406 x 467',467.0,594.9,436.6,412.2,35.8,58,90,15.2,0.0,183003.0,67831.0,17.5,10.7,8383.0,3291.0,10002.0,5034.0,NULL,NULL,'IS808_Rev',NULL); +INSERT INTO Columns VALUES(85,'UC 356 x 406 x 551',551.0,701.9,455.6,418.5,42.1,67.5,90,15.2,0.0,226938.0,82668.0,18.0,10.9,9962.0,3951.0,12076.0,6058.0,NULL,NULL,'IS808_Rev',NULL); +INSERT INTO Columns VALUES(86,'UC 356 x 406 x 634',633.9,807.5,474.6,424.0,47.6,77,90,15.2,0.0,274845.0,98122.0,18.4,11.0,11582.0,4628.0,14235.0,7108.0,NULL,NULL,'IS808_Rev',NULL); +CREATE TABLE IF NOT EXISTS "Beams" ( + "Id" INTEGER, + "Designation" VARCHAR(50), + "Mass" REAL(10 , 2), + "Area" REAL(10 , 2), + "D" REAL(10 , 2), + "B" REAL(10 , 2), + "tw" REAL(10 , 2), + "T" REAL(10 , 2), + "FlangeSlope" INTEGER, + "R1" REAL(10 , 2), + "R2" REAL(10 , 2), + "Iz" REAL(10 , 2), + "Iy" REAL(10 , 2), + "rz" REAL(10 , 2), + "ry" REAL(10 , 2), + "Zz" REAL(10 , 2), + "Zy" REAL(10 , 2), + "Zpz" REAL(10 , 2), + "Zpy" REAL(10 , 2), + "It" REAL(10 , 2), + "Iw" REAL(10 , 2), + "Source" VARCHAR(100), + "Type" VARCHAR(100), + PRIMARY KEY("Id") +); +INSERT INTO Beams VALUES(1,'JB 150',7.07,9.0,150.0,50.0,3.0,4.6,91.5,5.0,1.5,321.0,9.21,5.97,1.01,42.8,3.68,49.5,5.96,0.54800000000000004263,506.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(2,'JB 175',8.07,10.2,175.0,50.0,3.2,4.8,91.5,5.0,1.5,480.0,9.65,6.83,0.96899999999999995026,54.9,3.86,64.2,6.32,0.65600000000000004973,724.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(3,'JB 200',9.92,12.6,200.0,60.0,3.4,5.0,91.5,5.0,1.5,780.0,17.2,7.85,1.16,78.0,5.76,90.9,9.35,0.87300000000000004263,1710.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(4,'JB 225',12.78,16.2,225.0,80.0,3.7,5.0,91.5,6.5,1.5,1310.0,40.4,8.97,1.57,116.0,10.1,134.0,16.2,1.27,5160.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(5,'LB 75',6.05,7.71,75.0,50.0,3.7,5.0,91.5,6.5,2.0,72.7,10.0,3.07,1.13,19.3,4.0,22.3,6.39,0.73700000000000001065,127.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(6,'LB 100',8.01,10.2,100.0,50.0,4.0,6.4,91.5,7.0,3.0,168.0,12.7,4.05,1.11,33.6,5.08,38.9,8.2,1.38,292.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(7,'LB(P) 100',8.75,11.1,100.0,50.0,4.3,7.0,91.5,8.0,3.0,181.0,14.0,4.04,1.12,36.3,5.6,42.3,9.06,1.86,315.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(8,'LB 125',11.87,15.1,125.0,75.0,4.4,6.5,91.5,8.0,3.0,406.0,43.3,5.18,1.69,65.1,11.5,73.9,18.3,2.21,1600.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(9,'LB 150',14.19,18.0,150.0,80.0,4.8,6.8,91.5,9.5,3.0,687.0,55.2,6.16,1.74,91.7,13.8,104.0,22.1,3.02,2970.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(10,'LB 175',16.59,21.1,175.0,90.0,5.0,6.9,91.5,9.5,3.0,1090.0,79.5,7.18,1.94,124.0,17.6,141.0,28.2,3.55,5920.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(11,'LB(P) 175',16.6,21.1,175.0,80.0,5.2,7.7,96,9.5,3.0,1060.0,57.2,7.1,1.64,122.0,14.3,140.0,23.9,4.5,4590.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(12,'LB 200',19.83,25.2,200.0,100.0,5.4,7.3,91.5,9.5,3.0,1690.0,115.0,8.19,2.13,169.0,23.0,192.0,36.9,4.61,11200.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(13,'LB(P) 200',21.06,26.8,200.0,100.0,5.6,8.0,96,9.5,3.0,1800.0,112.0,8.19,2.05,180.0,22.5,205.0,37.7,6.27,12200.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(14,'LB 225',23.47,29.9,225.0,100.0,5.8,8.6,98,12.0,6.0,2500.0,112.0,9.14,1.94,222.0,22.5,254.0,39.2,8.47,16700.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(15,'LB 250',27.87,35.5,250.0,125.0,6.1,8.2,98,13.0,6.5,3720.0,193.0,10.2,2.33,297.0,30.9,338.0,55.3,10.2,39000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(16,'LB 275',32.96,42.0,275.0,140.0,6.4,8.8,98,14.0,7.0,5370.0,287.0,11.3,2.61,391.0,41.0,443.0,73.5,14.0,71200.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(17,'LB 300',37.72,48.0,300.0,150.0,6.7,9.4,98,15.0,7.5,7340.0,376.0,12.3,2.79,489.0,50.1,554.0,89.9,18.1,111000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(18,'LB(P) 300',41.5,52.8,300.0,140.0,7.0,11.6,98,15.0,7.5,8140.0,414.0,12.4,2.79,542.0,59.2,614.0,101.0,26.3,110000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(19,'LB 325',43.07,54.8,325.0,165.0,7.0,9.8,98,16.0,8.0,9880.0,510.0,13.4,3.05,608.0,61.9,688.0,111.0,22.8,182000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(20,'LB 350',49.44,63.0,350.0,165.0,7.4,11.4,98,16.0,8.0,13100.0,632.0,14.4,3.16,752.0,76.6,851.0,134.0,32.3,244000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(21,'LB 400',56.82,72.4,400.0,165.0,8.0,12.5,98,16.0,8.0,19300.0,716.0,16.3,3.14,965.0,86.8,1090.0,151.0,41.2,351000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(22,'LB 450',65.22,83.1,450.0,170.0,8.6,13.4,98,16.0,8.0,27500.0,853.0,18.2,3.2,1220.0,100.0,1400.0,174.0,51.8,522000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(23,'LB 500',74.92,95.4,500.0,180.0,9.2,14.1,98,17.0,8.5,38500.0,1060.0,20.1,3.33,1540.0,118.0,1770.0,206.0,65.5,808000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(24,'LB 550',86.28,109.0,550.0,190.0,9.9,15.0,98,18.0,9.0,53100.0,1330.0,21.9,3.48,1930.0,140.0,2220.0,246.0,84.5,1220000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(25,'LB 600',99.39,126.0,600.0,210.0,10.5,15.5,98,20.0,10.0,72900.0,1820.0,23.9,3.79,2430.0,173.0,2790.0,306.0,107.0,2040000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(26,'MB 100',8.95,11.4,100.0,50.0,4.7,7.0,98,9.0,4.5,182.0,12.5,3.99,1.04,36.4,5.01,42.6,8.58,2.15,315.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(27,'MB 125',13.35,17.0,125.0,70.0,5.0,8.0,98,9.0,4.5,445.0,38.4,5.11,1.5,71.3,10.9,82.1,18.4,3.99,1560.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(28,'MB 150',14.96,19.0,150.0,75.0,5.0,8.0,98,9.0,4.5,718.0,46.7,6.13,1.56,95.7,12.4,109.0,21.0,4.36,2830.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(29,'MB 175',19.5,24.8,175.0,85.0,5.8,9.0,98,10.0,5.0,1260.0,76.6,7.12,1.75,144.0,18.0,165.0,30.5,7.17,6340.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(30,'MB 200',24.17,30.8,200.0,100.0,5.7,10.0,98,11.0,5.5,2110.0,136.0,8.28,2.1,211.0,27.3,240.0,46.0,10.7,15000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(31,'MB 225',31.15,39.7,225.0,110.0,6.5,11.8,98,12.0,6.0,3440.0,218.0,9.31,2.34,306.0,39.6,348.0,66.3,18.6,29700.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(32,'MB 250',37.3,47.5,250.0,125.0,6.9,12.5,98,13.0,6.5,5130.0,334.0,10.3,2.65,410.0,53.5,465.0,89.7,25.5,57300.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(33,'MB 300',46.02,58.6,300.0,140.0,7.7,13.1,98,14.0,7.0,8990.0,486.0,12.3,2.87,599.0,69.4,681.0,117.0,34.7,123000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(34,'MB 350',52.33,66.7,350.0,140.0,8.1,14.2,98,14.0,7.0,13600.0,537.0,14.2,2.83,779.0,76.8,889.0,129.0,43.1,183000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(35,'MB 400',61.55,78.4,400.0,140.0,8.9,16.0,98,14.0,7.0,20400.0,622.0,16.1,2.81,1020.0,88.8,1170.0,149.0,59.6,269000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(36,'MB 450',72.38,92.2,450.0,150.0,9.4,17.4,98,15.0,7.5,30400.0,834.0,18.1,3.0,1350.0,111.0,1550.0,187.0,81.0,457000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(37,'MB 500',86.88,110.0,500.0,180.0,10.2,17.2,98,17.0,8.5,45200.0,1360.0,20.2,3.51,1800.0,152.0,2070.0,259.0,103.0,974000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(38,'MB 550',103.64,132.0,550.0,190.0,11.2,19.3,98,18.0,9.0,64900.0,1830.0,22.1,3.72,2360.0,193.0,2710.0,328.0,150.0,1550000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(39,'MB 600',121.0,154.0,600.0,210.0,12.0,20.3,98,20.0,10.0,90200.0,2570.0,24.1,4.08,3000.0,245.0,3450.0,418.0,198.0,2630000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(40,'NPB 100 X 55 X 8.1',8.1,10.3,100.0,55.0,4.1,5.7,90,7.0,0.0,171.0,15.9,4.07,1.24,34.2,5.78,39.4,9.14,1.15,351.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(41,'NPB 120 X 60 X 10.37',10.37,13.2,120.0,64.0,4.4,6.3,90,7.0,0.0,317.0,27.6,4.9,1.44,52.9,8.64,60.7,13.5,1.69,889.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(42,'NPB 140 X 70 X 12.89',12.89,16.4,140.0,73.0,4.7,6.9,90,7.0,0.0,541.0,44.9,5.74,1.65,77.3,12.3,88.3,19.2,2.4,1980.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(43,'NPB 160 X 80 X 15.77',15.77,20.0,160.0,82.0,5.0,7.4,90,9.0,0.0,869.0,68.3,6.57,1.84,108.0,16.6,123.0,26.1,3.54,3950.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(44,'NPB 180 X 90 X 15.37',15.37,19.5,177.0,91.0,4.3,6.5,90,9.0,0.0,1060.0,81.8,7.36,2.04,120.0,17.9,135.0,27.9,2.67,5930.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(45,'NPB 180 X 90 X 18.8',18.8,23.9,180.0,91.0,5.3,8.0,90,9.0,0.0,1310.0,100.0,7.41,2.05,146.0,22.1,166.0,34.6,4.72,7430.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(46,'NPB 180 X 90 X 21.27',21.27,27.0,182.0,92.0,6.0,9.0,90,9.0,0.0,1500.0,117.0,7.45,2.08,165.0,25.4,189.0,39.9,6.64,8730.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(47,'NPB 200 X 100 X 18.43',18.43,23.4,197.0,100.0,4.5,7.0,90,12.0,0.0,1590.0,117.0,8.23,2.23,161.0,23.4,181.0,36.5,4.13,10500.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(48,'NPB 200 X 100 X 22.36',22.36,28.4,200.0,100.0,5.6,8.5,90,12.0,0.0,1940.0,142.0,8.26,2.23,194.0,28.4,220.0,44.6,6.92,12900.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(49,'NPB 200 X 100 X 25.09',25.09,31.9,202.0,102.0,6.2,9.5,90,12.0,0.0,2210.0,168.0,8.31,2.29,218.0,33.1,249.0,51.8,9.36,15500.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(50,'NPB 200 X 130 X 27.37',27.37,34.8,207.0,133.0,5.8,8.5,90,12.0,0.0,2660.0,334.0,8.74,3.09,257.0,50.2,288.0,77.4,8.48,32800.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(51,'NPB 200 X 130 X 31.56',31.56,40.1,210.0,134.0,6.4,10.0,90,12.0,0.0,3150.0,401.0,8.85,3.16,300.0,59.9,337.0,92.4,12.8,40100.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(52,'NPB 200 X 150 X 30.46',30.46,38.7,194.0,150.0,6.0,9.0,90,12.0,0.0,2670.0,507.0,8.3,3.61,275.0,67.6,306.0,103.0,10.4,43300.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(53,'NPB 200 X 165 X 35.69',35.69,45.4,201.0,165.0,6.2,10.0,90,12.0,0.0,3410.0,749.0,8.66,4.06,339.0,90.8,376.0,138.0,14.5,68200.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(54,'NPB 200 X 165 X 42.48',42.48,54.1,205.0,166.0,7.2,12.0,90,12.0,0.0,4160.0,915.0,8.77,4.11,406.0,110.0,454.0,168.0,24.1,85100.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(55,'NPB 200 X 165 X 48.0',48.0,61.1,210.0,166.0,6.5,14.5,90,12.0,0.0,5020.0,1100.0,9.06,4.25,478.0,133.0,534.0,202.0,37.9,105000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(56,'NPB 220 X 110 X 22.18',22.18,28.2,217.0,110.0,5.0,7.7,90,12.0,0.0,2310.0,171.0,9.05,2.46,213.0,31.1,240.0,48.4,5.68,18700.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(57,'NPB 220 X 110 X 26.2',26.2,33.3,220.0,110.0,5.9,9.2,90,12.0,0.0,2770.0,204.0,9.11,2.47,251.0,37.2,285.0,58.1,9.03,22600.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(58,'NPB 220 X 110 X 29.35',29.35,37.3,222.0,112.0,6.6,10.2,90,12.0,0.0,3130.0,239.0,9.15,2.53,282.0,42.8,321.0,66.9,12.1,26700.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(59,'NPB 240 X 120 X 26.15',26.15,33.3,237.0,120.0,5.2,8.3,90,15.0,0.0,3290.0,240.0,9.93,2.68,277.0,40.0,311.0,62.3,8.51,31200.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(60,'NPB 240 X 120 X 30.71',30.71,39.1,240.0,120.0,6.2,9.8,90,15.0,0.0,3890.0,283.0,9.97,2.69,324.0,47.2,366.0,73.9,12.9,37300.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(61,'NPB 240 X 120 X 34.32',34.32,43.7,242.0,122.0,7.0,10.8,90,15.0,0.0,4360.0,328.0,9.99,2.74,361.0,53.8,410.0,84.3,17.1,43600.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(62,'NPB 250 X 125 X 30.11',30.11,38.3,250.0,125.0,6.0,9.0,90,15.0,0.0,4130.0,294.0,10.3,2.77,331.0,47.0,373.0,73.6,11.1,42500.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(63,'NPB 250 X 150 X 34.08',34.08,43.4,258.0,146.0,6.1,9.2,90,15.0,0.0,5120.0,478.0,10.8,3.32,396.0,65.5,444.0,101.0,12.8,73800.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(64,'NPB 250 X 150 X 39.78',39.78,50.6,262.0,147.0,6.6,11.2,90,15.0,0.0,6200.0,594.0,11.0,3.42,473.0,80.8,530.0,124.0,20.3,93200.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(65,'NPB 250 X 150 X 46.48',46.48,59.2,266.0,148.0,7.6,13.2,90,15.0,0.0,7380.0,715.0,11.1,3.47,554.0,96.6,625.0,149.0,31.5,113000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(66,'NPB 250 X 175 X 43.94',43.94,55.9,244.0,175.0,7.0,11.0,90,15.0,0.0,6090.0,984.0,10.4,4.19,499.0,112.0,555.0,172.0,22.4,133000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(67,'NPB 270 X 135 X 30.73',30.73,39.1,267.0,135.0,5.5,8.7,90,15.0,0.0,4910.0,357.0,11.2,3.02,368.0,53.0,412.0,82.3,10.4,59500.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(68,'NPB 270 X 135 X 36.07',36.07,45.9,270.0,135.0,6.6,10.2,90,15.0,0.0,5780.0,419.0,11.2,3.02,428.0,62.2,483.0,96.9,15.9,70500.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(69,'NPB 270 X 135 X 42.26',42.26,53.8,274.0,136.0,7.5,12.2,90,15.0,0.0,6940.0,513.0,11.3,3.08,507.0,75.5,574.0,117.0,25.0,87600.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(70,'NPB 300 X 150 X 36.53',36.53,46.5,297.0,150.0,6.1,9.2,90,15.0,0.0,7170.0,518.0,12.4,3.34,483.0,69.1,541.0,107.0,13.3,107000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(71,'NPB 300 X 150 X 42.24',42.24,53.8,300.0,150.0,7.1,10.7,90,15.0,0.0,8350.0,603.0,12.4,3.35,557.0,80.5,628.0,125.0,19.9,125000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(72,'NPB 300 X 150 X 49.32',49.32,62.8,304.0,152.0,8.0,12.7,90,15.0,0.0,9990.0,745.0,12.6,3.44,657.0,98.1,743.0,152.0,30.9,157000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(73,'NPB 300 X 165 X 39.88',39.88,50.7,310.0,165.0,5.8,9.7,90,15.0,0.0,8790.0,727.0,13.1,3.78,567.0,88.1,630.0,135.0,15.4,163000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(74,'NPB 300 X 165 X 45.76',45.76,58.2,313.0,166.0,6.6,11.2,90,15.0,0.0,10200.0,855.0,13.2,3.83,652.0,103.0,727.0,158.0,22.5,194000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(75,'NPB 300 X 165 X 53.46',53.46,68.1,317.0,167.0,7.6,13.2,90,15.0,0.0,12100.0,1020.0,13.3,3.88,764.0,122.0,857.0,189.0,35.2,236000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(76,'NPB 300 X 200 X 59.57',59.57,75.8,303.0,203.0,7.5,13.1,90,15.0,0.0,12800.0,1820.0,13.0,4.9,848.0,180.0,940.0,275.0,39.5,383000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(77,'NPB 300 X 200 X 66.75',66.75,85.0,306.0,204.0,8.5,14.6,90,15.0,0.0,14500.0,2060.0,13.0,4.93,948.0,202.0,1050.0,310.0,54.3,438000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(78,'NPB 300 X 200 X 75.37',75.37,96.0,310.0,205.0,9.4,16.6,90,15.0,0.0,16600.0,2380.0,13.1,4.98,1070.0,232.0,1200.0,356.0,77.6,512000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(79,'NPB 330 X 160 X 42.97',42.97,54.7,327.0,160.0,6.5,10.0,90,18.0,0.0,10200.0,685.0,13.6,3.53,625.0,85.6,701.0,133.0,19.6,171000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(80,'NPB 330 X 160 X 49.15',49.15,62.6,330.0,160.0,7.5,11.5,90,18.0,0.0,11700.0,788.0,13.7,3.54,713.0,98.5,804.0,153.0,28.0,199000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(81,'NPB 330 X 160 X 57.01',57.01,72.6,334.0,162.0,8.5,13.5,90,18.0,0.0,13900.0,960.0,13.8,3.63,832.0,118.0,942.0,184.0,42.2,245000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(82,'NPB 350 X 170 X 50.22',50.22,63.9,357.6,170.0,6.6,11.5,90,18.0,0.0,14500.0,944.0,15.0,3.84,811.0,111.0,906.0,171.0,27.3,281000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(83,'NPB 350 X 170 X 57.1',57.1,72.7,360.0,170.0,8.0,12.7,90,18.0,0.0,16200.0,1040.0,14.9,3.78,903.0,122.0,1010.0,191.0,37.4,313000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(84,'NPB 350 X 170 X 66.05',66.05,84.1,364.0,172.0,9.2,14.7,90,18.0,0.0,19000.0,1250.0,15.0,3.85,1040.0,145.0,1180.0,226.0,55.7,380000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(85,'NPB 350 X 250 X 79.18',79.18,100.0,340.0,250.0,9.0,14.0,90,18.0,0.0,21500.0,3650.0,14.6,6.01,1260.0,292.0,1400.0,446.0,63.4,968000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(86,'NPB 400 X 180 X 57.38',57.38,73.0,397.0,180.0,7.0,12.0,90,21.0,0.0,20200.0,1170.0,16.6,4.0,1020.0,130.0,1140.0,202.0,36.1,432000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(87,'NPB 400 X 180 X 66.31',66.31,84.4,400.0,180.0,8.6,13.5,90,21.0,0.0,23100.0,1310.0,16.5,3.95,1150.0,146.0,1300.0,229.0,51.3,490000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(88,'NPB 400 X 180 X 75.67',75.67,96.3,404.0,182.0,9.7,15.5,90,21.0,0.0,26700.0,1560.0,16.6,4.02,1320.0,171.0,1500.0,269.0,73.3,587000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(89,'NPB 400 X 200 X 67.28',67.28,85.7,400.0,200.0,8.0,13.0,90,21.0,0.0,24200.0,1730.0,16.8,4.5,1210.0,173.0,1350.0,269.0,48.5,648000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(90,'NPB 450 X190 X 67.16',67.16,85.5,447.0,190.0,7.6,13.1,90,21.0,0.0,29700.0,1500.0,18.6,4.19,1330.0,158.0,1490.0,245.0,47.1,704000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(91,'NPB 450 X 190 X 77.58',77.58,98.8,450.0,190.0,9.4,14.6,90,21.0,0.0,33700.0,1670.0,18.4,4.11,1490.0,176.0,1700.0,276.0,66.7,791000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(92,'NPB 450 X 190 X 92.37',92.37,117.0,456.0,192.0,11.0,17.6,90,21.0,0.0,40900.0,2080.0,18.6,4.21,1790.0,217.0,2040.0,340.0,109.0,997000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(93,'NPB 500 X 200 X 79.36',79.36,101.0,497.0,200.0,8.4,14.5,90,21.0,0.0,42900.0,1930.0,20.6,4.38,1720.0,193.0,1940.0,301.0,64.3,1120000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(94,'NPB 500 X 200 X 90.69',90.69,115.0,500.0,200.0,10.2,16.0,90,21.0,0.0,48100.0,2140.0,20.4,4.3,1920.0,214.0,2190.0,335.0,89.1,1240000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(95,'NPB 500 X 200 X 107.32',107.32,136.0,506.0,202.0,12.0,19.0,90,21.0,0.0,57700.0,2620.0,20.5,4.37,2280.0,259.0,2610.0,408.0,142.0,1540000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(96,'NPB 550 X 210 X 92.08',92.08,117.0,547.0,210.0,9.0,15.7,90,24.0,0.0,59900.0,2430.0,22.6,4.55,2190.0,231.0,2470.0,361.0,89.3,1710000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(97,'NPB 550 X 210 X 105.52',105.52,134.0,550.0,210.0,11.1,17.2,90,24.0,0.0,67100.0,2660.0,22.3,4.45,2440.0,254.0,2780.0,400.0,122.0,1880000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(98,'NPB 550 X 210 X 122.52',122.52,156.0,556.0,212.0,12.7,20.2,90,24.0,0.0,79100.0,3220.0,22.5,4.54,2840.0,304.0,3260.0,480.0,187.0,2299999.9999999993782,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(99,'NPB 600 X 220 X 107.57',107.57,137.0,597.0,220.0,9.8,17.5,90,24.0,0.0,82900.0,3110.0,24.6,4.76,2770.0,283.0,3140.0,442.0,122.0,2600000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(100,'NPB 600 X 220 X 122.45',122.45,155.0,600.0,220.0,12.0,19.0,90,24.0,0.0,92000.0,3380.0,24.2,4.66,3060.0,307.0,3510.0,485.0,165.0,2840000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(101,'NPB 600 X 220 X 154.47',154.47,196.0,610.0,224.0,15.0,24.0,90,24.0,0.0,118000.0,4520.0,24.5,4.79,3870.0,403.0,4470.0,640.0,316.0,3850000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(102,'NPB 700 X 250 X 113.46',113.46,144.0,694.0,250.0,9.0,16.0,90,24.0,0.0,118000.0,4170.0,28.6,5.37,3420.0,334.0,3850.0,518.0,107.0,4780000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(103,'NPB 700 X 250 X 128.41',128.41,163.0,695.0,250.0,11.5,16.5,90,24.0,0.0,128000.0,4310.0,27.9,5.13,3680.0,344.0,4210.0,543.0,136.0,4940000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(104,'NPB 700 X 250 X 143.42',143.42,182.0,700.0,250.0,12.5,19.0,90,24.0,0.0,145000.0,4960.0,28.2,5.21,4160.0,397.0,4760.0,625.0,190.0,5730000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(105,'NPB 700 X 250 X 153.87',153.87,196.0,704.0,250.0,13.0,21.0,90,24.0,0.0,159000.0,5480.0,28.4,5.29,4520.0,439.0,5170.0,690.0,240.0,6370000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(106,'NPB 700 X 250 X 171.48',171.48,218.0,709.0,250.0,14.5,23.5,90,24.0,0.0,178000.0,6140.0,28.5,5.3,5030.0,491.0,5770.0,775.0,328.0,7180000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(107,'NPB 750 X 270 X 145.29',145.29,185.0,750.0,265.0,13.2,16.6,90,17.0,0.0,161000.0,5160.0,29.5,5.28,4310.0,389.0,5000.0,616.0,150.0,6920000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(108,'NPB 750 X 270 X 174.54',174.54,222.0,760.0,270.0,14.4,21.6,90,17.0,0.0,206000.0,7100.0,30.4,5.65,5430.0,526.0,6240.0,827.0,272.0,9650000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(109,'NPB 750 X 270 X 202.49',202.49,257.0,770.0,270.0,15.6,26.6,90,17.0,0.0,249000.0,8750.0,31.1,5.82,6480.0,648.0,7430.0,1010.0,450.0,11999999.999999999644,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(110,'WB 150',17.0,21.6,150.0,100.0,5.4,7.0,96,8.0,4.0,839.0,94.7,6.22,2.09,111.0,18.9,126.0,31.9,4.22,5960.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(111,'WB 175',22.06,28.1,175.0,125.0,5.8,7.4,96,8.0,4.0,1510.0,188.0,7.32,2.59,172.0,30.1,194.0,51.2,6.22,16900.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(112,'WB 200',28.8,36.7,200.0,140.0,6.1,9.0,96,9.0,4.5,2620.0,328.0,8.45,2.99,262.0,46.9,294.0,78.7,11.3,37500.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(113,'WB 200',52.09,66.4,203.0,152.0,8.9,16.5,98,15.5,7.6,4780.0,809.0,8.48,3.49,470.0,106.0,539.0,175.0,65.8,83900.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(114,'WB 225',33.93,43.2,225.0,150.0,6.4,9.9,96,9.0,4.5,3920.0,448.0,9.52,3.22,348.0,59.8,389.0,99.7,15.5,64400.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(115,'WB 250',40.84,52.0,250.0,200.0,6.7,9.0,96,10.0,5.0,5940.0,857.0,10.6,4.05,475.0,85.7,527.0,149.0,17.8,174000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(116,'WB 300',48.12,61.3,300.0,200.0,7.4,10.0,96,11.0,5.5,9820.0,990.0,12.6,4.01,654.0,99.0,731.0,171.0,24.7,280000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(117,'WB 350',56.89,72.4,350.0,200.0,8.0,11.4,96,12.0,6.0,15500.0,1170.0,14.6,4.02,887.0,117.0,995.0,200.0,35.6,435000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(118,'WB 400',66.71,85.0,400.0,200.0,8.6,13.0,96,13.0,6.5,23400.0,1380.0,16.6,4.04,1170.0,138.0,1320.0,234.0,50.6,648000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(119,'WB 450',79.52,101.0,450.0,200.0,9.2,15.4,96,15.0,7.0,35100.0,1700.0,18.6,4.1,1560.0,170.0,1760.0,284.0,78.7,969000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(120,'WB 500',95.12,121.0,500.0,250.0,9.9,14.7,96,15.0,7.5,52200.0,2980.0,20.7,4.96,2090.0,239.0,2350.0,406.0,94.3,2250000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(121,'WB 550',112.48,143.0,550.0,250.0,10.5,17.6,96,16.0,8.0,74900.0,3740.0,22.8,5.1,2720.0,299.0,3060.0,500.0,145.0,3240000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(122,'WB 600',133.7,170.0,600.0,250.0,11.2,21.3,96,17.0,8.5,106000.0,4700.0,24.9,5.25,3540.0,376.0,3980.0,619.0,234.0,4640000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(123,'WB 600',145.06,184.0,600.0,250.0,11.8,23.6,96,18.0,9.0,115000.0,5290.0,25.0,5.35,3850.0,423.0,4340.0,692.0,305.0,5099999.9999999996447,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(124,'WPB 100 X 100 X 12.24',12.24,15.5,91.0,100.0,4.2,5.5,90,12.0,0.0,236.0,92.0,3.89,2.43,51.9,18.4,58.3,28.4,2.32,1670.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(125,'WPB 100 X 100 X 16.67',16.67,21.2,96.0,100.0,5.0,8.0,90,12.0,0.0,349.0,133.0,4.05,2.51,72.7,26.7,83.0,41.1,5.28,2580.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(126,'WPB 100 X 100 X 20.44',20.44,26.0,100.0,100.0,6.0,10.0,90,12.0,0.0,449.0,167.0,4.15,2.53,89.9,33.4,104.0,51.4,9.33,3370.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(127,'WPB 100 X 100 X 41.79',41.79,53.2,120.0,106.0,12.0,20.0,90,12.0,0.0,1140.0,399.0,4.63,2.73,190.0,75.3,235.0,116.0,67.2,9920.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(128,'WPB 120 X 120 X 14.56',14.56,18.5,109.0,120.0,4.2,5.5,90,12.0,0.0,413.0,158.0,4.72,2.92,75.8,26.4,84.1,40.6,2.59,4240.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(129,'WPB 120 X 120 X 19.89',19.89,25.3,114.0,120.0,5.0,8.0,90,12.0,0.0,606.0,230.0,4.89,3.01,106.0,38.4,119.0,58.8,6.04,6470.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(130,'WPB 120 X 120 X 26.7',26.7,34.0,120.0,120.0,6.5,11.0,90,12.0,0.0,864.0,317.0,5.04,3.05,144.0,52.9,165.0,80.9,13.9,9400.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(131,'WPB 120 X 120 X 52.13',52.13,66.4,140.0,126.0,12.5,21.0,90,12.0,0.0,2010.0,702.0,5.51,3.25,288.0,111.0,350.0,171.0,90.5,24700.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(132,'WPB 140 X 140 X 18.08',18.08,23.0,128.0,140.0,4.3,6.0,90,12.0,0.0,719.0,274.0,5.59,3.45,112.0,39.2,123.0,59.9,3.43,10200.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(133,'WPB 140 X 140 X 24.66',24.66,31.4,133.0,140.0,5.5,8.5,90,12.0,0.0,1030.0,389.0,5.73,3.52,155.0,55.6,173.0,84.8,8.1,15000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(134,'WPB 140 X 140 X 33.72',33.72,42.9,140.0,140.0,7.0,12.0,90,12.0,0.0,1500.0,549.0,5.92,3.57,215.0,78.5,245.0,119.0,20.1,22400.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(135,'WPB 140 X 140 X 63.24',63.24,80.5,160.0,146.0,13.0,22.0,90,12.0,0.0,3290.0,1140.0,6.39,3.76,411.0,156.0,493.0,240.0,118.0,54300.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(136,'WPB 150 X 150 X 23.5',23.5,29.9,152.0,152.0,5.8,6.8,90,12.0,0.0,1270.0,398.0,6.52,3.64,167.0,52.4,186.0,80.4,5.55,20900.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(137,'WPB 150 X 150 X 30.11',30.11,38.3,158.0,153.0,6.5,9.4,90,8.0,0.0,1760.0,561.0,6.77,3.82,222.0,73.4,248.0,111.0,10.6,30900.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(138,'WPB 150 X 150 X 36.97',36.97,47.0,162.0,154.0,8.0,11.5,90,8.0,0.0,2210.0,700.0,6.85,3.85,273.0,91.0,308.0,138.0,19.2,39600.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(139,'WPB 160 X 160 X 22.75',22.75,28.9,148.0,160.0,4.5,7.0,90,8.0,0.0,1220.0,478.0,6.5,4.06,165.0,59.7,181.0,90.5,4.54,23700.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(140,'WPB 160 X 160 X 30.44',30.44,38.7,152.0,160.0,6.0,9.0,90,15.0,0.0,1670.0,615.0,6.56,3.98,220.0,76.9,245.0,117.0,12.1,31400.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(141,'WPB 160 X 160 X 42.59',42.59,54.2,160.0,160.0,8.0,13.0,90,15.0,0.0,2490.0,889.0,6.77,4.04,311.0,111.0,353.0,169.0,31.2,47900.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(142,'WPB 160 X 160 X 76.19',76.19,97.0,180.0,166.0,14.0,23.0,90,15.0,0.0,5090.0,1750.0,7.24,4.25,566.0,211.0,674.0,325.0,160.0,108000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(143,'WPB 180 X 180 X 28.68',28.68,36.5,167.0,180.0,5.0,7.5,90,15.0,0.0,1960.0,729.0,7.33,4.47,235.0,81.1,258.0,123.0,8.32,46300.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(144,'WPB 180 X 180 X 35.52',35.52,45.2,171.0,180.0,6.0,9.5,90,15.0,0.0,2510.0,924.0,7.44,4.52,293.0,102.0,324.0,156.0,14.9,60200.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(145,'WPB 180 X 180 X 51.22',51.22,65.2,180.0,180.0,8.5,14.0,90,15.0,0.0,3830.0,1360.0,7.66,4.57,425.0,151.0,481.0,231.0,42.2,93700.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(146,'WPB 180 X180 X 88.9',88.9,113.0,200.0,186.0,14.5,24.0,90,15.0,0.0,7480.0,2580.0,8.12,4.77,748.0,277.0,883.0,425.0,201.0,199000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(147,'WPB 200 X 200 X 34.65',34.65,44.1,186.0,200.0,5.5,8.0,90,18.0,0.0,2940.0,1060.0,8.16,4.92,316.0,106.0,347.0,163.0,12.5,84400.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(148,'WPB 200 X 200 X 37.34',37.34,47.6,200.0,200.0,6.1,8.9,90,10.0,0.0,3628.0,1187.0,8.73,5.0,363.0,119.0,398.0,180.0,13.3,NULL,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(149,'WPB 200 X 200 X 42.26',42.26,53.8,190.0,200.0,6.5,10.0,90,18.0,0.0,3690.0,1330.0,8.28,4.98,388.0,133.0,429.0,203.0,21.0,108000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(150,'WPB 200 X 200 X 50.92',50.92,64.8,194.0,202.0,8.0,12.0,90,18.0,0.0,4530.0,1650.0,8.35,5.04,467.0,163.0,521.0,249.0,34.3,136000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(151,'WPB 200 X 200 X 61.3',61.3,78.0,200.0,200.0,9.0,15.0,90,18.0,0.0,5690.0,2000.0,8.54,5.06,569.0,200.0,642.0,305.0,59.7,171000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(152,'WPB 200 X 200 X 74.01',74.01,94.2,206.0,206.0,10.2,18.0,90,18.0,0.0,7170.0,2620.0,8.72,5.27,696.0,255.0,793.0,388.0,99.3,231000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(153,'WPB 200 X 200 X 83.52',83.52,106.0,209.0,209.0,13.0,19.5,90,18.0,0.0,8050.0,2970.0,8.7,5.28,771.0,284.0,888.0,435.0,134.0,266000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(154,'WPB 200 X 200 X 103.06',103.06,131.0,220.0,206.0,15.0,25.0,90,18.0,0.0,10600.0,3650.0,9.0,5.27,967.0,354.0,1130.0,543.0,257.0,346000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(155,'WPB 220 X 220 X 40.4',40.4,51.4,205.0,220.0,6.0,8.5,90,18.0,0.0,4170.0,1510.0,9.0,5.41,406.0,137.0,445.0,209.0,15.5,145000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(156,'WPB 220 X 220 X 50.51',50.51,64.3,210.0,220.0,7.0,11.0,90,18.0,0.0,5400.0,1950.0,9.16,5.51,515.0,177.0,568.0,270.0,28.6,193000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(157,'WPB 220 X 220 X 71.47',71.47,91.0,220.0,220.0,9.5,16.0,90,18.0,0.0,8090.0,2840.0,9.42,5.58,735.0,258.0,827.0,393.0,77.0,295000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(158,'WPB 220 X 220 X 115.61',115.61,147.0,226.0,226.0,15.5,26.0,90,18.0,0.0,12600.0,5010.0,9.28,5.83,1120.0,443.0,1310.0,677.0,311.0,500000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(159,'WPB 240 X 240 X 47.4',47.4,60.3,224.0,240.0,6.5,9.0,90,21.0,0.0,5830.0,2070.0,9.83,5.86,520.0,173.0,570.0,264.0,22.1,239000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(160,'WPB 240 X 240 X 60.32',60.32,76.8,230.0,240.0,7.5,12.0,90,21.0,0.0,7760.0,2760.0,10.0,6.0,675.0,230.0,744.0,351.0,42.1,328000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(161,'WPB 240 X 240 X 83.2',83.2,105.0,240.0,240.0,10.0,17.0,90,21.0,0.0,11200.0,3920.0,10.3,6.08,938.0,326.0,1050.0,498.0,103.0,486000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(162,'WPB 240 X 240 X 156.68',156.68,199.0,270.0,248.0,18.0,32.0,90,21.0,0.0,24200.0,8150.0,11.0,6.39,1790.0,657.0,2110.0,1000.0,626.0,1150000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(163,'WPB 250 X 250 X 67.22',67.22,85.6,247.0,252.0,11.0,11.1,90,24.0,0.0,9390.0,2960.0,10.4,5.89,760.0,235.0,851.0,364.0,51.3,411000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(164,'WPB 250 X 250 X 73.15',73.15,93.1,252.0,250.0,9.0,13.6,90,24.0,0.0,11000.0,3540.0,10.9,6.17,880.0,283.0,977.0,434.0,67.6,503000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(165,'WPB 250 X 250 X 85.04',85.04,108.0,253.0,255.0,14.0,14.1,90,24.0,0.0,12100.0,3910.0,10.5,6.0,961.0,306.0,1080.0,475.0,95.6,555000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(166,'WPB 250 X 250 X 97.04',97.04,123.0,260.0,256.0,12.7,17.6,90,24.0,0.0,15000.0,4930.0,11.0,6.31,1150.0,385.0,1300.0,591.0,140.0,722000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(167,'WPB 250 X 250 X 103.97',103.97,132.0,264.0,257.0,11.9,19.6,90,24.0,0.0,16700.0,5550.0,11.2,6.47,1270.0,432.0,1430.0,660.0,174.0,828000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(168,'WPB 250 X 250 X 117.58',117.58,149.0,269.0,259.0,13.5,22.1,90,24.0,0.0,19300.0,6410.0,11.3,6.54,1430.0,495.0,1630.0,757.0,244.0,975000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(169,'WPB 250 X 250 X 133.92',133.92,170.0,275.0,261.0,15.4,25.1,90,24.0,0.0,22500.0,7450.0,11.4,6.61,1640.0,571.0,1880.0,874.0,351.0,1160000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(170,'WPB 250 X 250 X 148.38',148.38,189.0,280.0,263.0,17.3,27.6,90,24.0,0.0,25400.0,8380.0,11.5,6.66,1810.0,637.0,2100.0,978.0,466.0,1330000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(171,'WPB 260 X 260 X 54.15',54.15,68.9,244.0,260.0,6.5,9.5,90,24.0,0.0,7980.0,2780.0,10.7,6.35,654.0,214.0,714.0,327.0,30.1,382000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(172,'WPB 260 X 260 X 68.16',68.16,86.8,250.0,260.0,7.5,12.5,90,24.0,0.0,10400.0,3660.0,10.9,6.5,836.0,282.0,919.0,430.0,54.2,516000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(173,'WPB 260 X 260 X 92.99',92.99,118.0,260.0,260.0,10.0,17.5,90,24.0,0.0,14900.0,5130.0,11.2,6.58,1140.0,394.0,1280.0,602.0,126.0,753000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(174,'WPB 260 X 260 X 114.4',114.4,145.0,268.0,262.0,12.5,21.5,90,24.0,0.0,18900.0,6450.0,11.3,6.65,1410.0,492.0,1590.0,752.0,224.0,978000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(175,'WPB 260 X 260 X 141.52',141.52,180.0,278.0,265.0,15.5,26.5,90,24.0,0.0,24300.0,8230.0,11.6,6.75,1750.0,621.0,2010.0,950.0,407.0,1290000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(176,'WPB 260 X 260 X 172.43',172.43,219.0,290.0,268.0,18.0,32.5,90,24.0,0.0,31300.0,10400.0,11.9,6.89,2150.0,779.0,2520.0,1190.0,720.0,1720000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(177,'WPB 280 X 280 X 61.26',61.26,78.0,264.0,280.0,7.0,10.0,90,24.0,0.0,10500.0,3660.0,11.6,6.85,799.0,261.0,873.0,399.0,35.5,590000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(178,'WPB 280 X 280 X 76.36',76.36,97.2,270.0,280.0,8.0,13.0,90,24.0,0.0,13600.0,4760.0,11.8,6.99,1010.0,340.0,1110.0,518.0,63.5,785000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(179,'WPB 280 X 280 X 188.54',188.54,240.0,310.0,288.0,18.5,33.0,90,24.0,0.0,39500.0,13100.0,12.8,7.4,2550.0,914.0,2960.0,1390.0,807.0,2520000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(180,'WPB 280 X 280 X 284.13',284.13,361.95,280.0,280.0,10.5,18.0,90,24.0,0.0,30682.9,9105.2,9.21,5.02,2191.6,650.4,2941.1,1406.8,146.0,1130000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(181,'WPB 300 X 300 X 69.8',69.8,88.9,283.0,300.0,7.5,10.5,90,27.0,0.0,13800.0,4730.0,12.4,7.29,975.0,315.0,1060.0,482.0,47.8,877000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(182,'WPB 300 X 300 X 88.34',88.34,112.0,290.0,300.0,8.5,14.0,90,27.0,0.0,18200.0,6300.0,12.7,7.48,1250.0,420.0,1380.0,641.0,87.8,1190000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(183,'WPB 300 X 300 X 100.85',100.85,128.0,294.0,300.0,10.0,16.0,90,27.0,0.0,21000.0,7210.0,12.8,7.49,1430.0,480.0,1580.0,733.0,124.0,1390000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(184,'WPB 300 X 300 X 117.03',117.03,149.0,300.0,300.0,11.0,19.0,90,27.0,0.0,25100.0,8560.0,12.9,7.57,1670.0,570.0,1860.0,870.0,189.0,1680000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(185,'WPB 300 X 300 X 237.92',237.92,303.0,340.0,310.0,21.0,39.0,90,27.0,0.0,59200.0,19400.0,13.9,8.0,3480.0,1250.0,4070.0,1910.0,1410.0,4380000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(186,'WPB 320 X 300 X 74.25',74.25,94.5,301.0,300.0,8.0,11.0,90,27.0,0.0,16400.0,4950.0,13.1,7.24,1090.0,330.0,1190.0,505.0,53.6,1040000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(187,'WPB 320 X 300 X 97.64',97.64,124.0,310.0,300.0,9.0,15.5,90,27.0,0.0,22900.0,6980.0,13.5,7.49,1470.0,465.0,1620.0,709.0,111.0,1510000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(188,'WPB 320 X 300 X 126.66',126.66,161.0,320.0,300.0,11.5,20.5,90,27.0,0.0,30800.0,9230.0,13.8,7.56,1920.0,615.0,2140.0,939.0,230.0,2060000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(189,'WPB 320 X 300 X 244.97',244.97,312.0,359.0,309.0,21.0,40.0,90,27.0,0.0,68100.0,19700.0,14.7,7.94,3790.0,1270.0,4430.0,1950.0,1500.0,5000000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(190,'WPB 340 X 300 X 78.9',78.9,100.0,320.0,300.0,8.5,11.5,90,27.0,0.0,19500.0,5180.0,13.9,7.18,1220.0,345.0,1340.0,529.0,60.1,1230000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(191,'WPB 340 X 300 X 104.78',104.78,133.0,330.0,300.0,9.5,16.5,90,27.0,0.0,27600.0,7430.0,14.4,7.46,1670.0,495.0,1850.0,755.0,131.0,1820000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(192,'WPB 340 X 300 X 134.16',134.16,170.0,340.0,300.0,12.0,21.5,90,27.0,0.0,36600.0,9680.0,14.6,7.53,2150.0,645.0,2400.0,985.0,262.0,2450000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(193,'WPB 340 X 300 X 290.64',290.64,315.0,377.0,309.0,21.0,40.0,90,27.0,0.0,76300.0,19700.0,15.5,7.9,4050.0,1270.0,4710.0,1950.0,1510.0,5580000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(194,'WPB 360 X 300 X 91.04',91.04,106.0,339.0,300.0,9.0,12.0,90,27.0,0.0,23000.0,5410.0,14.7,7.12,1350.0,360.0,1490.0,552.0,67.2,1440000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(195,'WPB 360 X 300 X 125.81',125.81,142.0,350.0,300.0,10.0,17.5,90,27.0,0.0,33000.0,7880.0,15.2,7.43,1890.0,525.0,2080.0,802.0,153.0,2170000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(196,'WPB 360 X 300 X 163.0',163.0,180.0,360.0,300.0,12.5,22.5,90,27.0,0.0,43100.0,10100.0,15.4,7.49,2390.0,676.0,2680.0,1030.0,298.0,2880000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(197,'WPB 360 X 300 X 250.27',250.27,318.0,395.0,308.0,21.0,40.0,90,27.0,0.0,84800.0,19500.0,16.3,7.82,4290.0,1260.0,4980.0,1940.0,1510.0,6130000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(198,'WPB 360 X 370 X 136.21',136.21,173.0,356.0,369.0,11.2,17.8,90,27.0,0.0,42100.0,14900.0,15.5,9.27,2360.0,808.0,2600.0,1220.0,192.0,4260000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(199,'WPB 360 X 370 X 150.87',150.87,192.0,360.0,370.0,12.3,19.8,90,27.0,0.0,47300.0,16700.0,15.6,9.33,2620.0,904.0,2900.0,1370.0,256.0,4830000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(200,'WPB 360 X 370 X 165.35',165.35,210.0,364.0,371.0,13.3,21.8,90,27.0,0.0,52500.0,18500.0,15.7,9.39,2880.0,1000.0,3200.0,1520.0,333.0,5430000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(201,'WPB 360 X 370 X 182.02',182.02,231.0,368.0,373.0,15.0,23.8,90,27.0,0.0,58200.0,20600.0,15.8,9.42,3160.0,1100.0,3530.0,1680.0,432.0,6090000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(202,'WPB 360 X 370 X 197.66',197.66,251.0,372.0,374.0,16.4,25.8,90,27.0,0.0,63900.0,22500.0,15.9,9.45,3430.0,1200.0,3850.0,1830.0,546.0,6740000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(203,'WPB 400 X 300 X 92.4',92.4,117.0,378.0,300.0,9.5,13.0,90,27.0,0.0,31200.0,5860.0,16.2,7.05,1650.0,390.0,1820.0,599.0,81.4,1940000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(204,'WPB 400 X 300 X 124.81',124.81,158.0,390.0,300.0,11.0,19.0,90,27.0,0.0,45000.0,8560.0,16.8,7.33,2310.0,570.0,2560.0,872.0,193.0,2940000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(205,'WPB 400 X 300 X 155.26',155.26,197.0,400.0,300.0,13.5,24.0,90,27.0,0.0,57600.0,10800.0,17.0,7.39,2880.0,721.0,3230.0,1100.0,361.0,3810000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(206,'WPB 400 X 300 X 255.74',255.74,325.0,432.0,307.0,21.0,40.0,90,27.0,0.0,104000.0,19300.0,17.8,7.7,4820.0,1250.0,5570.0,1930.0,1520.0,7410000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(207,'WPB 400 X 400 X 191.11',191.11,243.0,368.0,391.0,15.0,24.2,90,27.0,0.0,61500.0,24100.0,15.9,9.95,3340.0,1230.0,3730.0,1870.0,467.0,7120000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(208,'WPB 400 X 400 X 219.67',219.67,279.0,375.0,394.0,17.3,27.7,90,27.0,0.0,72100.0,28200.0,16.0,10.0,3840.0,1430.0,4320.0,2180.0,691.0,8510000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(209,'WPB 400 X 400 X 239.62',239.62,305.0,380.0,395.0,18.9,30.2,90,27.0,0.0,79700.0,31000.0,16.1,10.0,4190.0,1570.0,4750.0,2390.0,887.0,9480000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(210,'WPB 450 X 300 X 99.75',99.75,127.0,425.0,300.0,10.0,13.5,90,27.0,0.0,41800.0,6080.0,18.1,6.92,1970.0,405.0,2180.0,624.0,91.4,2570000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(211,'WPB 450 X 300 X 139.76',139.76,178.0,440.0,300.0,11.5,21.0,90,27.0,0.0,63700.0,9460.0,18.9,7.29,2890.0,631.0,3210.0,965.0,250.0,4140000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(212,'WPB 450 X 300 X 171.12',171.12,217.0,450.0,300.0,14.0,26.0,90,27.0,0.0,79800.0,11700.0,19.1,7.33,3550.0,781.0,3980.0,1190.0,448.0,5250000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(213,'WPB 450 X 300 X 263.33',263.33,335.0,478.0,307.0,21.0,40.0,90,27.0,0.0,131000.0,19300.0,19.7,7.59,5500.0,1250.0,6330.0,1930.0,1530.0,9250000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(214,'WPB 500 X 300 X 107.46',107.46,136.0,472.0,300.0,10.5,14.0,90,27.0,0.0,54600.0,6310.0,19.9,6.79,2310.0,420.0,2570.0,649.0,102.0,3299999.9999999998223,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(215,'WPB 500 X 300 X 129.78',129.78,165.0,480.0,300.0,11.5,18.0,90,27.0,0.0,68900.0,8110.0,20.4,7.0,2870.0,541.0,3190.0,832.0,179.0,4320000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(216,'WPB 500 X 300 X 155.08',155.08,197.0,490.0,300.0,12.0,23.0,90,27.0,0.0,86900.0,10300.0,20.9,7.24,3540.0,691.0,3940.0,1050.0,317.0,5640000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(217,'WPB 500 X 300 X 187.34',187.34,238.0,500.0,300.0,14.5,28.0,90,27.0,0.0,107000.0,12600.0,21.1,7.27,4280.0,841.0,4810.0,1290.0,548.0,7010000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(218,'WPB 500 X 300 X 270.28',270.28,344.0,524.0,306.0,21.0,40.0,90,27.0,0.0,161000.0,19100.0,21.6,7.45,6180.0,1250.0,7090.0,1930.0,1540.0,11100000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(219,'WPB 550 X 300 X 119.99',119.99,152.0,522.0,300.0,11.5,15.0,90,27.0,0.0,72800.0,6760.0,21.8,6.65,2790.0,451.0,3120.0,698.0,126.0,4330000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(220,'WPB 550 X 300 X 166.24',166.24,211.0,540.0,300.0,12.5,24.0,90,27.0,0.0,111000.0,10800.0,22.9,7.14,4140.0,721.0,4620.0,1100.0,360.0,7180000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(221,'WPB 550 X 300 X 199.44',199.44,254.0,550.0,300.0,15.0,29.0,90,27.0,0.0,136000.0,13000.0,23.1,7.17,4970.0,871.0,5590.0,1340.0,610.0,8850000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(222,'WPB 550 X 300 X 278.19',278.19,354.0,572.0,306.0,21.0,40.0,90,27.0,0.0,197000.0,19100.0,23.6,7.35,6920.0,1250.0,7930.0,1930.0,1550.0,13500000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(223,'WPB 600 X300 X 128.79',128.79,164.0,571.0,300.0,12.0,15.5,90,27.0,0.0,91800.0,6990.0,23.6,6.52,3210.0,466.0,3620.0,724.0,141.0,5380000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(224,'WPB 600 X 300 X 177.78',177.78,226.0,590.0,300.0,13.0,25.0,90,27.0,0.0,141000.0,11200.0,24.9,7.05,4780.0,751.0,5350.0,1150.0,407.0,8970000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(225,'WPB 600 X 300 X 211.92',211.92,269.0,600.0,300.0,15.5,30.0,90,27.0,0.0,171000.0,13500.0,25.1,7.08,5700.0,902.0,6420.0,1390.0,677.0,10900000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(226,'WPB 600 X 300 X 285.48',285.48,363.0,620.0,305.0,21.0,40.0,90,27.0,0.0,237000.0,18900.0,25.5,7.22,7650.0,1240.0,8770.0,1930.0,1560.0,15900000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(227,'WPB 650 X 300 X 137.98',137.98,175.0,620.0,300.0,12.5,16.0,90,27.0,0.0,113000.0,7220.0,25.4,6.41,3670.0,481.0,4150.0,750.0,158.0,6560000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(228,'WPB 650 X 300 X 189.69',189.69,241.0,640.0,300.0,13.5,26.0,90,27.0,0.0,175000.0,11700.0,26.9,6.96,5470.0,781.0,6130.0,1200.0,457.0,11000000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(229,'WPB 650 X 300 X 224.78',224.78,286.0,650.0,300.0,16.0,31.0,90,27.0,0.0,210000.0,13900.0,27.1,6.98,6480.0,932.0,7310.0,1440.0,749.0,13300000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(230,'WPB 650 X 300 X 293.39',293.39,373.0,668.0,305.0,21.0,40.0,90,27.0,0.0,281000.0,18900.0,27.4,7.12,8430.0,1240.0,9650.0,1930.0,1580.0,18600000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(231,'WPB 700 X 300 X 149.89',149.89,190.0,670.0,300.0,13.0,17.0,90,27.0,0.0,142000.0,7670.0,27.3,6.33,4260.0,511.0,4840.0,799.0,186.0,8150000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(232,'WPB 700 X 300 X 204.48',204.48,260.0,690.0,300.0,14.5,27.0,90,27.0,0.0,215000.0,12100.0,28.7,6.83,6240.0,811.0,7030.0,1250.0,521.0,13300000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(233,'WPB 700 X 300 X 240.51',240.51,306.0,700.0,300.0,17.0,32.0,90,27.0,0.0,256000.0,14400.0,28.9,6.86,7330.0,962.0,8320.0,1490.0,839.0,16000000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(234,'WPB 700 X 300 X 300.68',300.68,383.0,716.0,304.0,21.0,40.0,90,27.0,0.0,329000.0,18700.0,29.3,7.0,9190.0,1230.0,10500.0,1920.0,1590.0,21299999.999999998934,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(235,'WPB 800 X 300 X 171.52',171.52,218.0,770.0,300.0,14.0,18.0,90,30.0,0.0,208000.0,8130.0,30.9,6.1,5420.0,542.0,6220.0,856.0,243.0,11399999.999999999023,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(236,'WPB 800 X 300 X 224.38',224.38,285.0,790.0,300.0,15.0,28.0,90,30.0,0.0,303000.0,12600.0,32.5,6.65,7680.0,842.0,8690.0,1310.0,608.0,18200000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(237,'WPB 800 X 300 X 262.34',262.34,334.0,800.0,300.0,17.5,33.0,90,30.0,0.0,359000.0,14900.0,32.7,6.67,8970.0,993.0,10200.0,1550.0,958.0,21800000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(238,'WPB 800 X 300 X 317.36',317.36,404.0,814.0,303.0,21.0,40.0,90,30.0,0.0,442000.0,18600.0,33.0,6.78,10800.0,1220.0,12400.0,1930.0,1650.0,27700000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(239,'WPB 800 X 300 X 179.9',179.9,229.0,835.0,292.0,14.0,18.8,90,30.0,0.0,253000.0,7830.0,33.2,5.84,6080.0,536.0,7000.0,851.0,264.0,12900000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(240,'WPB 850 X 300 X 195.74',195.74,249.0,840.0,292.0,14.7,21.3,90,30.0,0.0,282000.0,8870.0,33.6,5.96,6720.0,608.0,7730.0,961.0,343.0,14799999.999999999822,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(241,'WPB 850 X 300 X 214.25',214.25,272.0,846.0,293.0,15.4,24.3,90,30.0,0.0,317000.0,10200.0,34.1,6.12,7500.0,698.0,8600.0,1100.0,459.0,17099999.999999999644,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(242,'WPB 850 X 300 X 230.56',230.56,293.0,851.0,294.0,16.1,26.8,90,30.0,0.0,347000.0,11300.0,34.4,6.23,8160.0,775.0,9350.0,1220.0,579.0,19199999.999999999289,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(243,'WPB 850 X 300 X 253.69',253.69,323.0,859.0,292.0,17.0,30.8,90,30.0,0.0,392000.0,12800.0,34.8,6.3,9130.0,879.0,10400.0,1380.0,802.0,21899999.999999999467,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(244,'WPB 900 X 300 X 198.01',198.01,252.0,870.0,300.0,15.0,20.0,90,30.0,0.0,301000.0,9040.0,34.5,5.98,6920.0,602.0,7990.0,957.0,321.0,16200000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(245,'WPB 900 X 300 X 251.62',251.62,320.0,890.0,300.0,16.0,30.0,90,30.0,0.0,422000.0,13500.0,36.2,6.5,9480.0,903.0,10800.0,1410.0,749.0,24900000.0,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(246,'WPB 900 X 300 X 291.46',291.46,371.0,900.0,300.0,18.5,35.0,90,30.0,0.0,494000.0,15800.0,36.4,6.52,10900.0,1050.0,12500.0,1650.0,1150.0,29399999.999999998578,'IS808_Rev',NULL); +INSERT INTO Beams VALUES(247,'UB 1016 x 305 x 222',222.0,282.8,970.3,300.0,16.0,21.1,90,30.0,0.0,407961.0,9534.0,38.0,5.8,8409.0,636.0,9807.0,1020.0,NULL,NULL,'',NULL); +INSERT INTO Beams VALUES(248,'UB 1016 x 305 x 249',248.7,316.9,980.2,300.0,16.5,26.0,90,30.0,0.0,481305.0,11743.0,39.0,6.1,9821.0,783.0,11350.0,1245.0,NULL,NULL,'',NULL); +INSERT INTO Beams VALUES(249,'UB 1016 x 305 x 272',272.3,346.9,990.1,300.0,16.5,31.0,90,30.0,0.0,553974.0,13993.0,40.0,6.4,11190.0,933.0,12826.0,1470.0,NULL,NULL,'',NULL); +INSERT INTO Beams VALUES(250,'UB 1016 x 305 x 314',314.3,400.4,1000.0,300.0,19.1,35.9,90,30.0,0.0,644211.0,16219.0,40.1,6.4,12884.0,1081.0,14851.0,1713.0,NULL,NULL,'',NULL); +INSERT INTO Beams VALUES(251,'UB 1016 x 305 x 349',349.4,445.2,1008.1,302.0,21.1,40.0,90,30.0,0.0,723131.0,18446.0,40.3,6.4,14346.0,1222.0,16592.0,1941.0,NULL,NULL,'',NULL); +INSERT INTO Beams VALUES(252,'UB 1016 x 305 x 393',392.7,500.2,1016.0,303.0,24.4,43.9,90,30.0,0.0,807688.0,20480.0,40.2,6.4,15899.0,1352.0,18539.0,2168.0,NULL,NULL,'',NULL); +INSERT INTO Beams VALUES(253,'UB 1016 x 305 x 437',436.9,556.6,1025.9,305.4,26.9,49.0,90,30.0,0.0,909906.0,23430.0,40.4,6.5,17739.0,1534.0,20762.0,2469.0,NULL,NULL,'',NULL); +INSERT INTO Beams VALUES(254,'UB 1016 x 305 x 487',486.6,619.9,1036.1,308.5,30.0,54.1,90,30.0,0.0,1021420.0,26703.0,40.6,6.6,19717.0,1731.0,23200.0,2800.0,NULL,NULL,'',NULL); +INSERT INTO Beams VALUES(255,'UB 127 x 76 x 13',13.0,16.5,127.0,76.0,4.0,7.6,90,7.6,0.0,473.0,55.7,5.4,1.8,75.0,15.0,84.2,22.6,NULL,NULL,'',NULL); +INSERT INTO Beams VALUES(256,'UB 152 x 89 x 16',16.0,20.3,152.0,88.7,4.5,7.7,90,7.6,0.0,834.0,89.7,6.4,2.1,109.0,20.0,123.0,31.2,NULL,NULL,'',NULL); +INSERT INTO Beams VALUES(257,'UB 178 x 102 x 19',19.0,24.3,178.0,101.2,4.8,7.9,90,7.6,0.0,1356.0,137.0,7.5,2.4,153.0,27.0,171.0,41.6,NULL,NULL,'',NULL); +INSERT INTO Beams VALUES(258,'UB 203 x 102 x 23',23.1,29.4,203.0,101.8,5.4,9.3,90,7.6,0.0,2105.0,164.0,8.5,2.4,207.0,32.0,234.0,49.8,NULL,NULL,'',NULL); +INSERT INTO Beams VALUES(259,'UB 203 x 133 x 25',25.1,32.0,203.0,133.2,5.7,7.8,90,7.6,0.0,2340.0,308.0,8.6,3.1,230.0,46.0,258.0,70.9,NULL,NULL,'',NULL); +INSERT INTO Beams VALUES(260,'UB 203 x 133 x 30',30.0,38.2,207.0,133.9,6.4,9.6,90,7.6,0.0,2896.0,385.0,8.7,3.2,280.0,57.0,314.0,88.2,NULL,NULL,'',NULL); +INSERT INTO Beams VALUES(261,'UB 254 x 102 x 22',22.0,28.0,254.0,101.6,5.7,6.8,90,7.6,0.0,2841.0,119.0,10.1,2.1,224.0,23.0,259.0,37.3,NULL,NULL,'',NULL); +INSERT INTO Beams VALUES(262,'UB 254 x 102 x 25',25.2,32.0,257.0,101.9,6.0,8.4,90,7.6,0.0,3415.0,149.0,10.3,2.2,266.0,29.0,306.0,46.0,NULL,NULL,'',NULL); +INSERT INTO Beams VALUES(263,'UB 254 x 102 x 28',28.3,36.1,260.0,102.2,6.3,10.0,90,7.6,0.0,4005.0,178.0,10.5,2.2,308.0,35.0,353.0,54.8,NULL,NULL,'',NULL); +INSERT INTO Beams VALUES(264,'UB 254 x 146 x 31',31.1,39.7,251.0,146.1,6.0,8.6,90,7.6,0.0,4413.0,447.0,10.5,3.4,351.0,61.0,393.0,94.1,NULL,NULL,'',NULL); +INSERT INTO Beams VALUES(265,'UB 254 x 146 x 37',37.0,47.2,256.0,146.4,6.3,10.9,90,7.6,0.0,5537.0,571.0,10.8,3.5,433.0,78.0,483.0,119.0,NULL,NULL,'',NULL); +INSERT INTO Beams VALUES(266,'UB 254 x 146 x 43',43.0,54.8,260.0,147.3,7.2,12.7,90,7.6,0.0,6544.0,677.0,10.9,3.5,504.0,92.0,566.0,141.0,NULL,NULL,'',NULL); +INSERT INTO Beams VALUES(267,'UB 305 x 102 x 25',24.8,31.6,305.0,101.6,5.8,7.0,90,7.6,0.0,4455.0,123.0,11.9,2.0,292.0,24.0,342.0,38.8,NULL,NULL,'',NULL); +INSERT INTO Beams VALUES(268,'UB 305 x 102 x 28',28.2,35.9,309.0,101.8,6.0,8.8,90,7.6,0.0,5366.0,155.0,12.2,2.1,348.0,31.0,403.0,48.5,NULL,NULL,'',NULL); +INSERT INTO Beams VALUES(269,'UB 305 x 102 x 33',32.8,41.8,313.0,102.4,6.6,10.8,90,7.6,0.0,6501.0,194.0,12.5,2.2,416.0,38.0,481.0,60.0,NULL,NULL,'',NULL); +INSERT INTO Beams VALUES(270,'UB 305 x 127 x 37',37.0,47.2,304.0,123.4,7.1,10.7,90,8.9,0.0,7171.0,336.0,12.3,2.7,471.0,54.0,539.0,85.4,NULL,NULL,'',NULL); +INSERT INTO Beams VALUES(271,'UB 305 x 127 x 42',41.9,53.4,307.0,124.3,8.0,12.1,90,8.9,0.0,8196.0,389.0,12.4,2.7,534.0,63.0,614.0,98.4,NULL,NULL,'',NULL); +INSERT INTO Beams VALUES(272,'UB 305 x 127 x 48',48.1,61.2,311.0,125.3,9.0,14.0,90,8.9,0.0,9575.0,461.0,12.5,2.7,616.0,74.0,711.0,116.0,NULL,NULL,'',NULL); +INSERT INTO Beams VALUES(273,'UB 305 x 165 x 40',40.3,51.3,303.0,165.0,6.0,10.2,90,8.9,0.0,8503.0,764.0,12.9,3.9,560.0,93.0,623.0,142.0,NULL,NULL,'',NULL); +INSERT INTO Beams VALUES(274,'UB 305 x 165 x 46',46.1,58.7,307.0,165.7,6.7,11.8,90,8.9,0.0,9899.0,896.0,13.0,3.9,646.0,108.0,720.0,166.0,NULL,NULL,'',NULL); +INSERT INTO Beams VALUES(275,'UB 305 x 165 x 54',54.0,68.8,310.0,166.9,7.9,13.7,90,8.9,0.0,11696.0,1063.0,13.0,3.9,754.0,127.0,846.0,196.0,NULL,NULL,'',NULL); +INSERT INTO Beams VALUES(276,'UB 356 x 127 x 33',33.1,42.1,349.0,125.4,6.0,8.5,90,10.2,0.0,8249.0,280.0,14.0,2.6,473.0,45.0,543.0,70.3,NULL,NULL,'',NULL); +INSERT INTO Beams VALUES(277,'UB 356 x 127 x 39',39.1,49.8,353.0,126.0,6.6,10.7,90,10.2,0.0,10172.0,358.0,14.3,2.7,576.0,57.0,659.0,89.1,NULL,NULL,'',NULL); +INSERT INTO Beams VALUES(278,'UB 356 x 171 x 45',45.0,57.3,351.0,171.1,7.0,9.7,90,10.2,0.0,12066.0,811.0,14.5,3.8,687.0,95.0,775.0,147.0,NULL,NULL,'',NULL); +INSERT INTO Beams VALUES(279,'UB 356 x 171 x 51',51.0,64.9,355.0,171.5,7.4,11.5,90,10.2,0.0,14136.0,968.0,14.8,3.9,796.0,113.0,896.0,174.0,NULL,NULL,'',NULL); +INSERT INTO Beams VALUES(280,'UB 356 x 171 x 57',57.0,72.6,358.0,172.2,8.1,13.0,90,10.2,0.0,16038.0,1108.0,14.9,3.9,896.0,129.0,1010.0,199.0,NULL,NULL,'',NULL); +INSERT INTO Beams VALUES(281,'UB 356 x 171 x 67',67.1,85.5,363.0,173.2,9.1,15.7,90,10.2,0.0,19463.0,1362.0,15.1,4.0,1071.0,157.0,1211.0,243.0,NULL,NULL,'',NULL); +INSERT INTO Beams VALUES(282,'UB 406 x 140 x 39',39.0,49.7,398.0,141.8,6.4,8.6,90,10.2,0.0,12508.0,410.0,15.9,2.9,629.0,58.0,724.0,91.0,NULL,NULL,'',NULL); +INSERT INTO Beams VALUES(283,'UB 406 x 140 x 46',46.0,58.6,403.0,142.2,6.8,11.2,90,10.2,0.0,15685.0,538.0,16.4,3.0,778.0,76.0,888.0,118.0,NULL,NULL,'',NULL); +INSERT INTO Beams VALUES(284,'UB 406 x 178 x 54',54.1,69.0,403.0,177.7,7.7,10.9,90,10.2,0.0,18722.0,1021.0,16.5,3.8,930.0,115.0,1055.0,178.0,NULL,NULL,'',NULL); +INSERT INTO Beams VALUES(285,'UB 406 x 178 x 60',60.1,76.5,406.0,177.9,7.9,12.8,90,10.2,0.0,21596.0,1203.0,16.8,4.0,1063.0,135.0,1199.0,209.0,NULL,NULL,'',NULL); +INSERT INTO Beams VALUES(286,'UB 406 x 178 x 67',67.1,85.5,409.0,178.8,8.8,14.3,90,10.2,0.0,24331.0,1365.0,16.9,4.0,1189.0,153.0,1346.0,237.0,NULL,NULL,'',NULL); +INSERT INTO Beams VALUES(287,'UB 406 x 178 x 74',74.2,94.5,413.0,179.5,9.5,16.0,90,10.2,0.0,27310.0,1545.0,17.0,4.0,1323.0,172.0,1501.0,267.0,NULL,NULL,'',NULL); +INSERT INTO Beams VALUES(288,'UB 457 x 152 x 52',52.3,66.6,450.0,152.4,7.6,10.9,90,10.2,0.0,21369.0,645.0,17.9,3.1,950.0,85.0,1096.0,133.0,NULL,NULL,'',NULL); +INSERT INTO Beams VALUES(289,'UB 457 x 152 x 60',59.8,76.2,455.0,152.9,8.1,13.3,90,10.2,0.0,25500.0,794.0,18.3,3.2,1122.0,104.0,1287.0,163.0,NULL,NULL,'',NULL); +INSERT INTO Beams VALUES(290,'UB 457 x 152 x 67',67.2,85.6,458.0,153.8,9.0,15.0,90,10.2,0.0,28927.0,912.0,18.4,3.3,1263.0,119.0,1453.0,187.0,NULL,NULL,'',NULL); +INSERT INTO Beams VALUES(291,'UB 457 x 152 x 74',74.2,94.5,462.0,154.4,9.6,17.0,90,10.2,0.0,32674.0,1046.0,18.6,3.3,1414.0,136.0,1627.0,213.0,NULL,NULL,'',NULL); +INSERT INTO Beams VALUES(292,'UB 457 x 152 x 82',82.1,104.5,466.0,155.3,10.5,18.9,90,10.2,0.0,36589.0,1184.0,18.7,3.4,1571.0,153.0,1811.0,240.0,NULL,NULL,'',NULL); +INSERT INTO Beams VALUES(293,'UB 457 x 191 x 67',67.1,85.5,453.0,189.9,8.5,12.7,90,10.2,0.0,29380.0,1452.0,18.5,4.1,1296.0,153.0,1471.0,237.0,NULL,NULL,'',NULL); +INSERT INTO Beams VALUES(294,'UB 457 x 191 x 74',74.3,94.6,457.0,190.4,9.0,14.5,90,10.2,0.0,33319.0,1671.0,18.8,4.2,1458.0,176.0,1653.0,272.0,NULL,NULL,'',NULL); +INSERT INTO Beams VALUES(295,'UB 457 x 191 x 82',82.0,104.5,460.0,191.3,9.9,16.0,90,10.2,0.0,37051.0,1871.0,18.8,4.2,1611.0,196.0,1831.0,304.0,NULL,NULL,'',NULL); +INSERT INTO Beams VALUES(296,'UB 457 x 191 x 89',89.3,113.8,463.0,191.9,10.5,17.7,90,10.2,0.0,41015.0,2089.0,19.0,4.3,1770.0,218.0,2014.0,338.0,NULL,NULL,'',NULL); +INSERT INTO Beams VALUES(297,'UB 457 x 191 x 98',98.3,125.3,467.0,192.8,11.4,19.6,90,10.2,0.0,45727.0,2347.0,19.1,4.3,1957.0,243.0,2232.0,379.0,NULL,NULL,'',NULL); +INSERT INTO Beams VALUES(298,'UB 533 x 210 x 101',101.0,128.7,537.0,210.0,10.8,17.4,90,12.7,0.0,61519.0,2691.0,21.9,4.6,2292.0,256.0,2612.0,399.0,NULL,NULL,'',NULL); +INSERT INTO Beams VALUES(299,'UB 533 x 210 x 109',109.0,138.9,540.0,210.8,11.6,18.8,90,12.7,0.0,66822.0,2942.0,21.9,4.6,2477.0,279.0,2828.0,436.0,NULL,NULL,'',NULL); +INSERT INTO Beams VALUES(300,'UB 533 x 210 x 122',122.0,155.4,545.0,211.9,12.7,21.3,90,12.7,0.0,76043.0,3387.0,22.1,4.7,2793.0,320.0,3196.0,500.0,NULL,NULL,'',NULL); +INSERT INTO Beams VALUES(301,'UB 533 x 210 x 82',82.2,104.7,528.0,208.8,9.6,13.2,90,12.7,0.0,47539.0,2007.0,21.3,4.4,1800.0,192.0,2059.0,300.0,NULL,NULL,'',NULL); +INSERT INTO Beams VALUES(302,'UB 533 x 210 x 92',92.1,117.4,533.0,209.3,10.1,15.6,90,12.7,0.0,55227.0,2389.0,21.7,4.5,2072.0,228.0,2360.0,356.0,NULL,NULL,'',NULL); +INSERT INTO Beams VALUES(303,'UB 610 x 229 x 101',101.2,128.9,603.0,227.6,10.5,14.8,90,12.7,0.0,75780.0,2914.0,24.2,4.8,2515.0,256.0,2881.0,400.0,NULL,NULL,'',NULL); +INSERT INTO Beams VALUES(304,'UB 610 x 229 x 113',113.0,143.9,608.0,228.2,11.1,17.3,90,12.7,0.0,87318.0,3433.0,24.6,4.9,2874.0,301.0,3281.0,469.0,NULL,NULL,'',NULL); +INSERT INTO Beams VALUES(305,'UB 610 x 229 x 125',125.1,159.3,612.0,229.0,11.9,19.6,90,12.7,0.0,98610.0,3932.0,24.9,5.0,3221.0,343.0,3676.0,535.0,NULL,NULL,'',NULL); +INSERT INTO Beams VALUES(306,'UB 610 x 229 x 140',139.9,178.2,617.0,230.2,13.1,22.1,90,12.7,0.0,111777.0,4505.0,25.0,5.0,3622.0,391.0,4142.0,611.0,NULL,NULL,'',NULL); +INSERT INTO Beams VALUES(307,'UB 610 x 305 x 149',149.2,190.0,612.0,304.8,11.8,19.7,90,16.5,0.0,125876.0,9306.0,25.7,7.0,4111.0,611.0,4594.0,937.0,NULL,NULL,'',NULL); +INSERT INTO Beams VALUES(308,'UB 610 x 305 x 179',179.0,228.1,620.0,307.1,14.1,23.6,90,16.5,0.0,153024.0,11407.0,25.9,7.1,4935.0,743.0,5547.0,1144.0,NULL,NULL,'',NULL); +INSERT INTO Beams VALUES(309,'UB 610 x 305 x 238',238.1,303.3,636.0,311.4,18.4,31.4,90,16.5,0.0,209471.0,15835.0,26.3,7.2,6589.0,1017.0,7486.0,1574.0,NULL,NULL,'',NULL); +INSERT INTO Beams VALUES(310,'UB 686 x 254 x 125',125.2,159.5,678.0,253.0,11.7,16.2,90,15.2,0.0,117992.0,4382.0,27.2,5.2,3481.0,346.0,3994.0,542.0,NULL,NULL,'',NULL); +INSERT INTO Beams VALUES(311,'UB 686 x 254 x 140',140.1,178.4,684.0,253.7,12.4,19.0,90,15.2,0.0,136267.0,5182.0,27.6,5.4,3987.0,409.0,4558.0,638.0,NULL,NULL,'',NULL); +INSERT INTO Beams VALUES(312,'UB 686 x 254 x 152',152.4,194.1,688.0,254.5,13.2,21.0,90,15.2,0.0,150355.0,5783.0,27.8,5.5,4374.0,454.0,5000.0,710.0,NULL,NULL,'',NULL); +INSERT INTO Beams VALUES(313,'UB 686 x 254 x 170',170.2,216.8,693.0,255.8,14.5,23.7,90,15.2,0.0,170326.0,6629.0,28.0,5.5,4916.0,518.0,5631.0,811.0,NULL,NULL,'',NULL); +INSERT INTO Beams VALUES(314,'UB 762 x 267 x 134',133.9,170.6,750.0,264.4,12.0,15.5,90,16.5,0.0,150692.0,4786.0,29.7,5.3,4018.0,362.0,4644.0,570.0,NULL,NULL,'',NULL); +INSERT INTO Beams VALUES(315,'UB 762 x 267 x 147',146.9,187.2,754.0,265.2,12.8,17.5,90,16.5,0.0,168501.0,5454.0,30.0,5.4,4470.0,411.0,5156.0,647.0,NULL,NULL,'',NULL); +INSERT INTO Beams VALUES(316,'UB 762 x 267 x 173',173.0,220.4,762.0,266.7,14.3,21.6,90,16.5,0.0,205282.0,6848.0,30.5,5.6,5387.0,514.0,6198.0,807.0,NULL,NULL,'',NULL); +INSERT INTO Beams VALUES(317,'UB 762 x 267 x 197',196.8,250.6,770.0,268.0,15.6,25.4,90,16.5,0.0,239957.0,8173.0,30.9,5.7,6234.0,610.0,7167.0,959.0,NULL,NULL,'',NULL); +INSERT INTO Beams VALUES(318,'UB 914 x 305 x 201',200.9,255.9,903.0,303.3,15.1,20.2,90,19.1,0.0,325254.0,9420.0,35.7,6.1,7204.0,621.0,8351.0,982.0,NULL,NULL,'',NULL); +INSERT INTO Beams VALUES(319,'UB 914 x 305 x 224',224.2,285.6,910.4,304.1,15.9,23.9,90,19.1,0.0,376413.0,11233.0,36.3,6.3,8269.0,739.0,9535.0,1163.0,NULL,NULL,'',NULL); +INSERT INTO Beams VALUES(320,'UB 914 x 305 x 253',253.4,322.8,918.4,305.5,17.3,27.9,90,19.1,0.0,436304.0,13298.0,36.8,6.4,9501.0,871.0,10942.0,1371.0,NULL,NULL,'',NULL); +INSERT INTO Beams VALUES(321,'UB 914 x 305 x 289',289.1,368.3,926.6,307.7,19.5,32.0,90,19.1,0.0,504187.0,15594.0,37.0,6.5,10883.0,1014.0,12570.0,1601.0,NULL,NULL,'',NULL); +INSERT INTO Beams VALUES(322,'UB 914 x 419 x 343',343.3,437.3,911.8,418.5,19.4,32.0,90,24.1,0.0,625779.0,39149.0,37.8,9.5,13726.0,1871.0,15477.0,2890.0,NULL,NULL,'',NULL); +INSERT INTO Beams VALUES(323,'UB 914 x 419 x 388',388.0,494.2,921.0,420.5,21.4,36.6,90,24.1,0.0,719635.0,45431.0,38.2,9.6,15627.0,2161.0,17665.0,3341.0,NULL,NULL,'',NULL); +CREATE TABLE IF NOT EXISTS "Channels" ( + "Id" INTEGER, + "Designation" VARCHAR(50), + "Mass" REAL(10 , 2), + "Area" REAL(10 , 2), + "D" INTEGER(50), + "B" INTEGER(50), + "tw" REAL(10 , 2), + "T" REAL(10 , 2), + "FlangeSlope" INTEGER, + "R1" REAL(10 , 2), + "R2" REAL(10 , 2), + "Cy" REAL(10 , 2), + "Iz" REAL(10 , 2), + "Iy" REAL(10 , 2), + "rz" REAL(10 , 2), + "ry" REAL(10 , 2), + "Zz" REAL(10 , 2), + "Zy" REAL(10 , 2), + "Zpz" REAL(10 , 2), + "Zpy" REAL, + "It" REAL(10 , 2), + "Iw" REAL(10 , 2), + "Source" VARCHAR(100), + "Type" VARCHAR(100), + PRIMARY KEY("Id") +); +INSERT INTO Channels VALUES(1,'MC 75',7.14,9.08,75,40,4.8,7.5,96,8.5,2.4,1.32,78.2,12.7,2.94,1.18,20.9,4.8,25.0,9.0,1.59,132.0,'IS808_Rev',NULL); +INSERT INTO Channels VALUES(2,'MC 100',9.56,12.1,100,50,5.0,7.7,96,9.0,2.4,1.54,191.0,26.3,3.97,1.47,38.4,7.6,45.2,14.8,2.25,512.0,'IS808_Rev',NULL); +INSERT INTO Channels VALUES(3,'MC 125',13.1,16.6,125,65,5.3,8.2,96,9.5,2.4,1.95,424.0,60.3,5.05,1.9,67.9,13.3,78.9,26.0,3.59,1900.0,'IS808_Rev',NULL); +INSERT INTO Channels VALUES(4,'MC 125*',13.7,17.4,125,66,6.0,8.1,96,9.5,2.4,1.92,433.0,63.7,4.98,1.91,69.4,13.6,81.2,27.1,3.9,2030.0,'IS808_Rev',NULL); +INSERT INTO Channels VALUES(5,'MC 150',16.8,21.3,150,75,5.7,9.0,96,10.0,2.4,2.2,786.0,102.0,6.08,2.19,104.0,19.3,121.0,38.1,5.45,4700.0,'IS808_Rev',NULL); +INSERT INTO Channels VALUES(6,'MC 150*',17.7,22.5,150,76,6.5,9.0,96,10.0,2.4,2.17,810.0,108.0,6.0,2.2,108.0,20.0,126.0,40.0,6.07,5060.0,'IS808_Rev',NULL); +INSERT INTO Channels VALUES(7,'MC 175',19.6,24.8,175,75,6.0,10.2,96,10.5,3.2,2.19,1230.0,120.0,7.04,2.2,141.0,22.7,163.0,44.7,7.49,7450.0,'IS808_Rev',NULL); +INSERT INTO Channels VALUES(8,'MC 175*',22.7,27.3,175,76,7.5,10.2,96,10.5,3.2,2.14,1290.0,130.0,6.87,2.18,147.0,23.7,174.0,47.4,9.01,8250.0,'IS808_Rev',NULL); +INSERT INTO Channels VALUES(9,'MC 200',22.3,28.4,200,75,6.2,11.4,96,11.0,3.2,2.2,1820.0,139.0,8.02,2.21,182.0,26.2,212.0,51.2,9.89,11000.0,'IS808_Rev',NULL); +INSERT INTO Channels VALUES(10,'MC 200*',24.3,30.9,200,76,7.5,11.4,96,11.0,3.2,2.12,1900.0,149.0,7.85,2.2,190.0,27.3,224.0,53.8,11.4,12100.0,'IS808_Rev',NULL); +INSERT INTO Channels VALUES(11,'MC 225',26.1,33.2,225,80,6.5,12.4,96,12.0,3.2,2.31,2700.0,185.0,9.02,2.36,240.0,32.7,279.0,63.8,13.3,18700.0,'IS808_Rev',NULL); +INSERT INTO Channels VALUES(12,'MC 225*',30.7,38.7,225,82,9.0,12.4,96,12.0,3.2,2.22,2920.0,210.0,8.69,2.33,260.0,35.1,309.0,68.6,17.6,22000.0,'IS808_Rev',NULL); +INSERT INTO Channels VALUES(13,'MC 250',30.6,38.9,250,80,7.2,14.1,96,12.0,3.2,2.3,3820.0,218.0,9.92,2.37,306.0,38.2,358.0,74.2,18.9,26800.0,'IS808_Rev',NULL); +INSERT INTO Channels VALUES(14,'MC 250*',34.2,43.4,250,82,9.0,14.1,96,12.0,3.2,2.23,4060.0,242.0,9.68,2.36,325.0,40.7,386.0,78.7,22.8,30600.0,'IS808_Rev',NULL); +INSERT INTO Channels VALUES(15,'MC 250*',38.1,48.1,250,83,11.0,14.1,96,12.0,3.2,2.19,4280.0,258.0,9.44,2.32,342.0,42.1,414.0,80.8,28.5,33800.0,'IS808_Rev',NULL); +INSERT INTO Channels VALUES(16,'MC 300',36.3,46.2,300,90,7.8,13.6,96,13.0,3.2,2.35,6400.0,311.0,11.7,2.59,427.0,46.8,501.0,91.9,21.7,57500.0,'IS808_Rev',NULL); +INSERT INTO Channels VALUES(17,'MC 300*',41.5,52.7,300,92,10.0,13.6,96,13.0,3.2,2.26,6880.0,344.0,11.4,2.55,458.0,49.6,549.0,96.3,28.1,66100.0,'IS808_Rev',NULL); +INSERT INTO Channels VALUES(18,'MC 300*',46.2,58.4,300,93,12.0,13.6,96,13.0,3.2,2.22,7260.0,363.0,11.1,2.49,484.0,51.2,589.0,98.7,36.1,72300.0,'IS808_Rev',NULL); +INSERT INTO Channels VALUES(19,'MC 350',42.7,54.3,350,100,8.3,13.5,96,14.0,4.8,2.44,10000.0,429.0,13.6,2.81,575.0,56.8,677.0,112.0,26.1,112000.0,'IS808_Rev',NULL); +INSERT INTO Channels VALUES(20,'MC 400',50.1,63.7,400,100,8.8,15.3,96,15.0,4.8,2.42,15100.0,504.0,15.4,2.81,758.0,66.5,898.0,129.0,36.1,170000.0,'IS808_Rev',NULL); +INSERT INTO Channels VALUES(21,'JC 100',5.8,7.41,100,45,3.0,5.1,91.5,6.0,2.0,1.4,123.0,14.6,4.09,1.4,24.8,4.7,28.4,8.8,0.51900000000000003907,264.0,'IS808_Rev',NULL); +INSERT INTO Channels VALUES(22,'JC 125',7.9,10.0,125,50,3.0,6.6,91.5,6.0,2.4,1.64,269.0,25.1,5.17,1.58,43.1,7.5,49.1,13.6,1.07,701.0,'IS808_Rev',NULL); +INSERT INTO Channels VALUES(23,'JC 150',9.9,12.6,150,55,3.6,6.9,91.5,7.0,2.4,1.67,471.0,37.4,6.1,1.72,62.9,9.8,72.1,18.1,1.47,1520.0,'IS808_Rev',NULL); +INSERT INTO Channels VALUES(24,'JC 175',11.2,14.2,175,60,3.6,6.9,91.5,7.0,3.0,1.75,720.0,49.6,7.11,1.87,82.3,11.7,94.2,21.9,1.62,2780.0,'IS808_Rev',NULL); +INSERT INTO Channels VALUES(25,'JC 200',14.0,17.7,200,70,4.1,7.1,91.5,8.0,3.2,1.97,1160.0,82.8,8.08,2.16,116.0,16.5,133.0,31.0,2.23,6150.0,'IS808_Rev',NULL); +INSERT INTO Channels VALUES(26,'LC 75',5.7,7.26,75,40,3.7,6.0,91.5,6.0,2.0,1.35,65.9,11.3,3.01,1.25,17.6,4.3,20.6,7.7,0.72599999999999997868,110.0,'IS808_Rev',NULL); +INSERT INTO Channels VALUES(27,'LC 100',7.9,10.0,100,50,4.0,6.4,91.5,6.0,2.0,1.62,164.0,24.4,4.05,1.56,32.9,7.2,38.1,13.3,1.11,434.0,'IS808_Rev',NULL); +INSERT INTO Channels VALUES(28,'LC 125',10.7,13.6,125,65,4.4,6.6,91.5,7.0,2.4,2.04,356.0,56.3,5.11,2.03,57.1,12.6,65.4,23.4,1.68,1590.0,'IS808_Rev',NULL); +INSERT INTO Channels VALUES(29,'LC (P) 125',11.3,14.3,125,65,4.6,7.0,96,7.0,2.4,1.87,370.0,50.6,5.08,1.88,59.2,10.9,68.3,22.0,2.27,1670.0,'IS808_Rev',NULL); +INSERT INTO Channels VALUES(30,'LC 150',14.4,18.3,150,75,4.8,7.8,91.5,8.0,2.4,2.39,697.0,101.0,6.16,2.35,93.1,19.9,106.0,36.5,3.04,4120.0,'IS808_Rev',NULL); +INSERT INTO Channels VALUES(31,'LC (P) 150',15.6,19.8,150,75,5.0,8.7,96,8.0,2.4,2.24,750.0,96.1,6.15,2.2,100.0,18.3,114.0,35.7,4.55,4450.0,'IS808_Rev',NULL); +INSERT INTO Channels VALUES(32,'LC 175',17.6,22.4,175,75,5.1,9.5,91.5,8.0,3.2,2.4,1140.0,124.0,7.16,2.36,131.0,24.4,150.0,44.7,5.07,6830.0,'IS808_Rev',NULL); +INSERT INTO Channels VALUES(33,'LC 200',20.6,26.2,200,75,5.5,10.8,91.5,8.5,3.2,2.36,1720.0,144.0,8.11,2.35,172.0,28.2,199.0,51.8,7.31,10300.0,'IS808_Rev',NULL); +INSERT INTO Channels VALUES(34,'LC (P) 200',21.5,27.3,200,75,5.7,11.4,96,8.5,3.2,2.23,1790.0,136.0,8.09,2.23,179.0,25.9,207.0,50.2,9.13,10800.0,'IS808_Rev',NULL); +INSERT INTO Channels VALUES(35,'LC 225',24.0,30.5,225,90,5.8,10.2,96,11.0,3.2,2.47,2550.0,207.0,9.14,2.6,226.0,31.8,260.0,64.2,9.3,22200.0,'IS808_Rev',NULL); +INSERT INTO Channels VALUES(36,'LC 250',28.0,35.6,250,100,6.1,10.7,96,11.0,3.2,2.71,3690.0,295.0,10.1,2.88,295.0,40.6,338.0,82.5,12.0,39700.0,'IS808_Rev',NULL); +INSERT INTO Channels VALUES(37,'LC 300',33.1,42.1,300,100,6.7,11.6,96,12.0,3.2,2.56,6050.0,344.0,11.9,2.86,403.0,46.3,467.0,94.2,15.6,66400.0,'IS808_Rev',NULL); +INSERT INTO Channels VALUES(38,'LC (P) 300',33.1,42.1,300,90,7.0,12.5,96,12.0,3.2,2.32,5910.0,282.0,11.8,2.59,394.0,42.4,460.0,84.3,16.8,53000.0,'IS808_Rev',NULL); +INSERT INTO Channels VALUES(39,'LC 350',38.9,49.4,350,100,7.4,12.5,96,13.0,4.8,2.42,9310.0,391.0,13.7,2.81,532.0,51.6,623.0,103.0,20.3,103000.0,'IS808_Rev',NULL); +INSERT INTO Channels VALUES(40,'LC 400',45.8,58.2,400,100,8.0,14.0,96,14.0,4.8,2.37,13900.0,457.0,15.5,2.8,699.0,60.0,825.0,117.0,27.9,157000.0,'IS808_Rev',NULL); +INSERT INTO Channels VALUES(41,'MPC 75',7.14,9.1,75,40,4.8,7.5,90,8.5,4.5,1.38,78.6,13.7,2.94,1.23,21.0,5.2,25.2,9.5,1.48,132.0,'IS808_Rev',NULL); +INSERT INTO Channels VALUES(42,'MPC 100',9.56,12.1,100,50,5.0,7.7,90,9.0,4.5,1.65,193.0,29.4,3.98,1.55,38.6,8.8,45.5,16.0,2.04,512.0,'IS808_Rev',NULL); +INSERT INTO Channels VALUES(43,'MPC 125',13.1,16.7,125,65,5.5,8.1,90,9.5,5.0,2.14,426.0,69.8,5.04,2.04,68.2,15.9,79.3,29.1,3.14,1900.0,'IS808_Rev',NULL); +INSERT INTO Channels VALUES(44,'MPC 125*',13.7,17.5,125,66,6.0,8.1,90,9.5,5.0,2.11,437.0,74.1,5.0,2.06,69.9,16.5,81.7,30.2,3.4,2030.0,'IS808_Rev',NULL); +INSERT INTO Channels VALUES(45,'MPC 150',16.8,21.3,150,75,5.7,9.0,90,10.0,5.0,2.46,792.0,120.0,6.09,2.37,105.0,23.8,122.0,43.2,4.71,4700.0,'IS808_Rev',NULL); +INSERT INTO Channels VALUES(46,'MPC 150*',17.7,22.5,150,76,6.5,9.0,90,10.0,5.0,2.4,817.0,128.0,6.02,2.38,109.0,24.7,126.0,45.1,5.25,5060.0,'IS808_Rev',NULL); +INSERT INTO Channels VALUES(47,'MPC 175',19.6,24.9,175,75,6.0,10.2,90,10.5,6.0,2.39,1240.0,138.0,7.06,2.36,141.0,27.0,164.0,49.5,6.66,7450.0,'IS808_Rev',NULL); +INSERT INTO Channels VALUES(48,'MPC 175*',21.7,27.6,175,77,7.5,10.2,90,10.5,6.0,2.32,1310.0,155.0,6.9,2.37,150.0,28.9,176.0,53.0,8.1,8550.0,'IS808_Rev',NULL); +INSERT INTO Channels VALUES(49,'MPC 200',22.3,28.4,200,75,6.2,11.4,90,11.0,6.0,2.34,1830.0,157.0,8.03,2.35,183.0,30.5,213.0,55.9,8.99,11000.0,'IS808_Rev',NULL); +INSERT INTO Channels VALUES(50,'MPC 200*',24.3,30.9,200,76,7.5,11.4,90,11.0,6.5,2.26,1910.0,168.0,7.86,2.33,191.0,31.5,225.0,57.8,10.4,12100.0,'IS808_Rev',NULL); +INSERT INTO Channels VALUES(51,'MPC 225',26.1,33.2,225,80,6.5,12.4,90,12.0,6.5,2.48,2710.0,208.0,9.03,2.5,241.0,37.9,280.0,69.5,12.1,18700.0,'IS808_Rev',NULL); +INSERT INTO Channels VALUES(52,'MPC 225*',30.7,39.0,225,83,9.0,12.4,90,12.0,7.0,2.37,2960.0,244.0,8.72,2.51,263.0,41.3,312.0,75.3,16.2,22800.0,'IS808_Rev',NULL); +INSERT INTO Channels VALUES(53,'MPC 250',30.6,38.9,250,80,7.2,14.1,90,12.0,7.0,2.44,3830.0,240.0,9.93,2.48,307.0,43.2,359.0,79.3,17.5,26800.0,'IS808_Rev',NULL); +INSERT INTO Channels VALUES(54,'MPC 250*',34.2,43.4,250,82,9.0,14.1,90,12.0,7.0,2.36,4080.0,267.0,9.69,2.48,326.0,45.9,387.0,83.5,21.1,30600.0,'IS808_Rev',NULL); +INSERT INTO Channels VALUES(55,'MPC 250*',38.1,48.6,250,84,11.0,14.1,90,12.0,7.0,2.31,4350.0,295.0,9.46,2.46,348.0,48.4,420.0,87.7,26.7,34900.0,'IS808_Rev',NULL); +INSERT INTO Channels VALUES(56,'MPC 300',36.3,46.2,300,90,7.8,13.6,90,13.0,7.0,2.54,6420.0,351.0,11.7,2.76,428.0,54.4,502.0,99.1,19.8,57500.0,'IS808_Rev',NULL); +INSERT INTO Channels VALUES(57,'MPC 300*',41.5,52.8,300,92,10.0,13.6,90,13.0,7.0,2.42,6910.0,390.0,11.4,2.72,460.0,57.5,551.0,103.0,25.7,66100.0,'IS808_Rev',NULL); +INSERT INTO Channels VALUES(58,'MPC 300*',46.2,58.8,300,94,12.0,13.6,90,13.0,7.0,2.36,7360.0,424.0,11.1,2.69,490.0,60.3,596.0,108.0,33.5,74400.0,'IS808_Rev',NULL); +INSERT INTO Channels VALUES(59,'MPC 350',42.7,54.3,350,100,8.3,13.5,90,14.0,8.0,2.65,10100.0,497.0,13.6,3.02,577.0,67.7,679.0,122.0,23.4,112000.0,'IS808_Rev',NULL); +INSERT INTO Channels VALUES(60,'MPC 400',50.1,63.8,400,100,8.8,15.3,90,15.0,8.0,2.6,15200.0,572.0,15.4,3.0,762.0,77.4,901.0,139.0,33.1,170000.0,'IS808_Rev',NULL); +CREATE TABLE IF NOT EXISTS "SHS" ( + "Id" INTEGER NOT NULL, + "Designation" VARCHAR, + "D" REAL(10 , 2), + "B" REAL(10 , 2), + "T" REAL(10 , 2), + "W" REAL(10 , 2), + "A" REAL(10 , 2), + "Izz" REAL(10 , 2), + "Iyy" REAL(10 , 2), + "Rzz" REAL(10 , 2), + "Ryy" REAL(10 , 2), + "Zzz" REAL(10 , 2), + "Zyy" REAL(10 , 2), + "Zpz" REAL(10 , 2), + "Zpy" REAL(10 , 2), + "Source" VARCHAR(50), + PRIMARY KEY("Id") +); +INSERT INTO SHS VALUES(1,' SHS 25 x 25 x 2.6',25.0,25.0,2.6,1.69,2.16,1.72,1.72,0.89,0.89,1.38,1.38,1.76,1.76,'IS 4923:1997'); +INSERT INTO SHS VALUES(2,' SHS 25 x 25 x 3.2',25.0,25.0,3.2,1.98,2.53,1.89,1.89,0.86,0.86,1.51,1.51,1.98,1.98,'IS 4923:1997'); +INSERT INTO SHS VALUES(3,' SHS 30 x 30 x 2.6',30.0,30.0,2.6,2.1,2.68,3.23,3.23,1.1,1.1,2.15,2.15,2.68,2.68,'IS 4923:1997'); +INSERT INTO SHS VALUES(4,' SHS 30 x 30 x 3.2',30.0,30.0,3.2,2.49,3.17,3.62,3.62,1.07,1.07,2.41,2.41,3.08,3.08,'IS 4923:1997'); +INSERT INTO SHS VALUES(5,' SHS 30 x 30 x 4.0',30.0,30.0,4.0,2.94,3.75,3.97,3.97,1.03,1.03,2.64,2.64,3.5,3.5,'IS 4923:1997'); +INSERT INTO SHS VALUES(6,' SHS 32 x 32 x 2.6',32.0,32.0,2.6,2.26,2.88,4.02,4.02,1.18,1.18,2.51,2.51,3.11,3.11,'IS 4923:1997'); +INSERT INTO SHS VALUES(7,' SHS 32 x 32 x 3.2',32.0,32.0,3.2,2.69,3.42,4.54,4.54,1.15,1.15,2.84,2.84,3.59,3.59,'IS 4923:1997'); +INSERT INTO SHS VALUES(8,' SHS 32 x 32 x 4.0',32.0,32.0,4.0,3.19,4.07,5.02,5.02,1.11,1.11,3.14,3.14,4.11,4.11,'IS 4923:1997'); +INSERT INTO SHS VALUES(9,' SHS 35 x 35 x 2.6',35.0,35.0,2.6,2.51,3.2,5.43,5.43,1.3,1.3,3.1,3.1,3.81,3.81,'IS 4923:1997'); +INSERT INTO SHS VALUES(10,' SHS 35 x 35 x 3.2',35.0,35.0,3.2,2.99,3.81,6.18,6.18,1.27,1.27,3.53,3.53,4.42,4.42,'IS 4923:1997'); +INSERT INTO SHS VALUES(11,' SHS 35 x 35 x 4.0',35.0,35.0,4.0,3.57,4.55,6.93,6.93,1.23,1.23,3.96,3.96,5.11,5.11,'IS 4923:1997'); +INSERT INTO SHS VALUES(12,' SHS 38 x 38 x 2.6',38.0,38.0,2.6,2.75,3.51,7.14,7.14,1.43,1.43,3.76,3.76,4.57,4.57,'IS 4923:1997'); +INSERT INTO SHS VALUES(13,' SHS 38 x 38 x 2.9',38.0,38.0,2.9,3.03,3.86,7.68,7.68,1.41,1.41,4.04,4.04,4.97,4.97,'IS 4923:1997'); +INSERT INTO SHS VALUES(14,' SHS 38 x 38 x 3.2',38.0,38.0,3.2,3.29,4.19,8.18,8.18,2.4,2.4,4.3,4.3,5.34,5.34,'IS 4923:1997'); +INSERT INTO SHS VALUES(15,' SHS 38 x 38 x 3.6',38.0,38.0,3.6,3.63,4.62,8.76,8.76,1.38,1.38,4.61,4.61,5.8,5.8,'IS 4923:1997'); +INSERT INTO SHS VALUES(16,' SHS 38 x 38 x 4.0',38.0,38.0,4.0,3.95,5.03,9.26,9.26,1.36,1.36,4.87,4.87,6.22,6.22,'IS 4923:1997'); +INSERT INTO SHS VALUES(17,' SHS 40 x 40 x 2.6',40.0,40.0,2.6,2.92,3.72,8.45,8.45,1.51,1.51,4.22,4.22,5.12,5.12,'IS 4923:1997'); +INSERT INTO SHS VALUES(18,' SHS 40 x 40 x 3.2',40.0,40.0,3.2,3.49,4.45,9.72,9.72,1.48,1.48,4.86,4.86,6.01,6.01,'IS 4923:1997'); +INSERT INTO SHS VALUES(19,' SHS 40 x 40 x 3.6',40.0,40.0,3.6,3.85,4.91,10.45,10.45,1.46,1.46,5.22,5.22,6.53,6.53,'IS 4923:1997'); +INSERT INTO SHS VALUES(20,' SHS 40 x 40 x 4.0',40.0,40.0,4.0,4.2,5.35,11.07,11.07,1.44,1.44,5.54,5.54,7.01,7.01,'IS 4923:1997'); +INSERT INTO SHS VALUES(21,' SHS 45 x 45 x 2.6',45.0,45.0,2.6,3.32,4.24,12.47,12.47,1.71,1.71,5.52,5.52,6.64,6.64,'IS 4923:1997'); +INSERT INTO SHS VALUES(22,' SHS 45 x 45 x 2.9',45.0,45.0,2.9,3.66,4.67,13.45,13.45,1.7,1.7,5.98,5.98,7.25,7.25,'IS 4923:1997'); +INSERT INTO SHS VALUES(23,' SHS 45 x 45 x 3.2',45.0,45.0,3.2,3.99,5.09,14.41,14.41,1.68,1.68,6.4,6.4,7.83,7.83,'IS 4923:1997'); +INSERT INTO SHS VALUES(24,' SHS 45 x 45 x 3.6',45.0,45.0,3.6,4.42,5.63,15.57,15.57,1.66,1.66,6.92,6.92,8.55,8.55,'IS 4923:1997'); +INSERT INTO SHS VALUES(25,' SHS 45 x 45 x 4.5',45.0,45.0,4.5,5.31,6.77,17.74,17.74,1.62,1.62,7.88,7.88,9.99,9.99,'IS 4923:1997'); +INSERT INTO SHS VALUES(26,' SHS 49.5 x 49.5 x 2.9',49.0,49.0,2.9,4.07,5.19,18.37,18.37,1.88,1.88,7.42,7.42,8.93,8.93,'IS 4923:1997'); +INSERT INTO SHS VALUES(27,' SHS 49.5 x 49.5 x 3.6',49.0,49.0,3.6,4.93,6.28,21.42,21.42,1.85,1.85,8.66,8.66,10.6,10.6,'IS 4923:1997'); +INSERT INTO SHS VALUES(28,' SHS 49.5 x 49.5 x 4.5',49.0,49.0,4.5,5.95,7.58,24.64,24.64,1.8,1.8,9.96,9.96,12.47,12.47,'IS 4923:1997'); +INSERT INTO SHS VALUES(29,' SHS 63.5 x 63.5 x 3.2',63.0,63.0,3.2,5.85,7.45,44.35,44.35,2.44,2.44,13.97,13.97,16.65,16.65,'IS 4923:1997'); +INSERT INTO SHS VALUES(30,' SHS 63.5 x 63.5 x 3.6',63.0,63.0,3.6,6.51,8.29,48.55,48.55,2.42,2.42,15.29,15.29,18.36,18.36,'IS 4923:1997'); +INSERT INTO SHS VALUES(31,' SHS 63.5 x 63.5 x 4.5',63.0,63.0,4.5,7.93,10.1,57.0,57.0,2.38,2.38,17.95,17.95,21.93,21.93,'IS 4923:1997'); +INSERT INTO SHS VALUES(32,' SHS 72 x 72 x 3.2',72.0,72.0,3.2,6.71,8.54,66.32,66.32,2.79,2.79,18.42,18.42,21.8,21.8,'IS 4923:1997'); +INSERT INTO SHS VALUES(33,' SHS 72 x 72 x 4.0',72.0,72.0,4.0,8.22,10.47,79.03,79.03,2.75,2.75,21.95,21.95,26.32,26.32,'IS 4923:1997'); +INSERT INTO SHS VALUES(34,' SHS 72 x 72 x 4.8',72.0,72.0,4.8,9.66,12.31,90.31,90.31,2.71,2.71,25.09,25.09,30.49,30.49,'IS 4923:1997'); +INSERT INTO SHS VALUES(35,' SHS 75 x 75 x 3.2',75.0,75.0,3.2,7.01,8.93,75.53,75.53,2.91,2.91,20.41,20.41,23.79,23.79,'IS 4923:1997'); +INSERT INTO SHS VALUES(36,' SHS 75 x 75 x 4.0',75.0,75.0,4.0,8.59,10.95,90.19,90.19,2.87,2.87,24.05,24.05,28.76,28.76,'IS 4923:1997'); +INSERT INTO SHS VALUES(37,' SHS 75 x 75 x 4.9',75.0,75.0,4.9,10.3,13.12,104.82,104.82,2.83,2.83,27.95,27.95,33.92,33.92,'IS 4923:1997'); +INSERT INTO SHS VALUES(38,' SHS 88.9 x 88.9 x 3.6',88.0,88.0,3.6,9.38,11.95,142.83,142.83,3.46,3.46,32.13,32.13,37.85,37.85,'IS 4923:1997'); +INSERT INTO SHS VALUES(39,' SHS 88.9 x 88.9 x 4.5',88.0,88.0,4.5,11.52,14.67,170.97,170.97,3.41,3.41,38.46,38.46,45.55,45.55,'IS 4923:1997'); +INSERT INTO SHS VALUES(40,' SHS 88.9 x 88.9 x 4.9',88.0,88.0,4.9,12.44,15.85,182.57,182.57,3.39,3.39,41.07,41.07,49.23,49.23,'IS 4923:1997'); +INSERT INTO SHS VALUES(41,' SHS 91.5 x 91.5 x 3.6',91.0,91.0,3.6,9.67,12.32,156.49,156.49,3.56,3.56,34.21,34.21,40.24,40.24,'IS 4923:1997'); +INSERT INTO SHS VALUES(42,' SHS 91.5 x 91.5 x 4.5',91.0,91.0,4.5,11.88,15.14,187.57,187.57,3.52,3.52,41.0,41.0,48.79,48.79,'IS 4923:1997'); +INSERT INTO SHS VALUES(43,' SHS 91.5 x 91.5 x 5.4',91.0,91.0,5.4,14.01,17.85,215.68,215.68,3.48,3.48,47.14,47.14,56.77,56.77,'IS 4923:1997'); +INSERT INTO SHS VALUES(44,' SHS 100 x 100 x 4.0',100.0,100.0,4.0,11.73,14.95,226.35,226.35,3.89,3.89,45.27,45.27,53.3,53.3,'IS 4923:1997'); +INSERT INTO SHS VALUES(45,' SHS 100 x 100 x 5.0',100.0,100.0,5.0,14.41,18.36,271.1,271.1,3.84,3.84,54.22,54.22,64.59,64.59,'IS 4923:1997'); +INSERT INTO SHS VALUES(46,' SHS 100 x 100 x 6.0',100.0,100.0,6.0,16.98,21.63,311.47,311.47,3.79,3.79,62.29,62.29,75.1,75.1,'IS 4923:1997'); +INSERT INTO SHS VALUES(47,' SHS 113.5 x 113.5 x 4.5',113.0,113.0,4.5,14.99,19.1,372.88,372.88,4.42,4.42,65.71,65.71,77.33,77.33,'IS 4923:1997'); +INSERT INTO SHS VALUES(48,' SHS 113.5 x 113.5 x 4.8',113.0,113.0,4.8,15.92,20.28,393.31,393.31,4.4,4.4,69.3,69.3,81.81,81.81,'IS 4923:1997'); +INSERT INTO SHS VALUES(49,' SHS 113.5 x 113.5 x 5.4',113.0,113.0,5.4,17.74,22.6,432.58,432.58,4.38,4.38,76.23,76.23,90.55,90.55,'IS 4923:1997'); +INSERT INTO SHS VALUES(50,' SHS 113.5 x I13.5 x 6.0',113.0,113.0,6.0,19.53,24.87,469.81,469.81,4.35,4.35,82.79,82.79,98.96,98.96,'IS 4923:1997'); +INSERT INTO SHS VALUES(51,' SHS 125 x 125 x 4.5',125.0,125.0,4.5,16.62,21.17,505.83,505.83,4.89,4.89,80.93,80.93,94.54,94.54,'IS 4923:1997'); +INSERT INTO SHS VALUES(52,' SHS 125 x 125 x 5.0',125.0,125.0,5.0,18.33,23.36,552.62,552.62,4.86,4.86,88.42,88.42,104.1,104.1,'IS 4923:1997'); +INSERT INTO SHS VALUES(53,' SHS 125 x 125 x 6.0',125.0,125.0,6.0,21.69,27.63,640.89,640.89,4.82,4.82,102.54,102.54,121.87,121.87,'IS 4923:1997'); +INSERT INTO SHS VALUES(54,' SHS 132 x 132 x 4.8',132.0,132.0,4.8,18.71,23.83,634.39,634.39,5.16,5.16,96.12,96.12,112.69,112.69,'IS 4923:1997'); +INSERT INTO SHS VALUES(55,' SHS 132 x 132 x 5.4',132.0,132.0,5.4,20.88,26.59,700.11,700.11,5.13,5.13,106.08,106.08,125.02,125.02,'IS 4923:1997'); +INSERT INTO SHS VALUES(56,' SHS 132 x 132 x 6.0',132.0,132.0,6.0,23.01,29.31,762.98,762.98,5.1,5.1,115.6,115.6,136.98,136.98,'IS 4923:1997'); +INSERT INTO SHS VALUES(57,' SHS 150 x 150 x 5.0',150.0,150.0,5.0,22.26,28.36,982.12,982.12,5.89,5.89,130.95,130.95,152.98,152.98,'IS 4923:1997'); +INSERT INTO SHS VALUES(58,' SHS 150 x 150 x 6.0',150.0,150.0,6.0,26.4,33.63,1145.91,1145.91,5.84,5.84,152.79,152.79,179.88,179.88,'IS 4923:1997'); +INSERT INTO SHS VALUES(59,' SHS 180 x 180 x 4.0',180.0,180.0,4.0,21.9,27.9,1434.0,1434.0,7.17,7.17,159.0,159.0,184.0,184.0,'IS 4923:1997'); +INSERT INTO SHS VALUES(60,' SHS 180 x 180 x 5.0',180.0,180.0,5.0,27.2,34.6,1755.0,1755.0,7.12,7.12,195.0,195.0,226.0,226.0,'IS 4923:1997'); +INSERT INTO SHS VALUES(61,' SHS 180 x 180 x 6.0',180.0,180.0,6.0,32.05,40.83,2036.0,2036.0,7.06,7.06,226.0,226.0,280.0,280.0,'IS 4923:1997'); +INSERT INTO SHS VALUES(62,' SHS 180 x 180 x 8.0',180.0,180.0,8.0,41.91,53.39,2590.73,2590.73,6.97,6.97,287.86,287.86,340.68,340.68,'IS 4923:1997'); +CREATE TABLE IF NOT EXISTS "RHS" ( + "Id" INTEGER NOT NULL, + "Designation" VARCHAR, + "D" REAL(10 , 2), + "B" REAL(10 , 2), + "T" REAL(10 , 2), + "W" REAL(10 , 2), + "A" REAL(10 , 2), + "Izz" REAL(10 , 2), + "Iyy" REAL(10 , 2), + "Rzz" REAL(10 , 2), + "Ryy" REAL(10 , 2), + "Zzz" REAL(10 , 2), + "Zyy" REAL(10 , 2), + "Zpz" REAL(10 , 2), + "Zpy" REAL(10 , 2), + "Source" VARCHAR(50), + PRIMARY KEY("Id") +); +INSERT INTO RHS VALUES(1,' RHS 50 x 25 x 2.9',50.0,25.0,2.9,2.98,3.8,10.93,3.6,1.7,0.97,4.37,2.88,5.72,3.48,'IS 4923:1997'); +INSERT INTO RHS VALUES(2,' RHS 50 x 25 x 3.2',50.0,25.0,3.2,3.24,4.13,11.63,3.8,1.68,0.96,4.65,3.04,6.14,3.73,'IS 4923:1997'); +INSERT INTO RHS VALUES(3,' RHS 60 x 40 x 2.9',60.0,40.0,2.9,4.12,5.25,24.74,13.11,2.17,1.58,8.25,6.56,10.25,7.73,'IS 4923:1997'); +INSERT INTO RHS VALUES(4,' RHS 66 x 33 x 2.9',66.0,33.0,2.9,4.07,5.19,27.33,9.12,2.29,1.33,8.28,5.53,10.59,6.49,'IS 4923:1997'); +INSERT INTO RHS VALUES(5,' RHS 66 x 33 x 3.6',66.0,33.0,3.6,4.93,6.28,31.87,10.52,2.25,1.29,9.66,6.37,12.56,7.66,'IS 4923:1997'); +INSERT INTO RHS VALUES(6,' RHS 66 x 33 x 4.5',66.0,33.0,4.5,5.95,7.58,36.64,11.93,2.2,1.25,11.1,7.23,14.77,8.94,'IS 4923:1997'); +INSERT INTO RHS VALUES(7,' RHS 70 x 30 x 2.9',70.0,30.0,2.9,4.12,5.25,29.82,7.72,2.38,1.21,8.52,5.14,11.07,6.04,'IS 4923:1997'); +INSERT INTO RHS VALUES(8,' RHS 70 x 30 x 3.2',70.0,30.0,3.2,4.5,5.73,32.04,8.24,2.37,1.2,9.15,5.49,11.98,6.51,'IS 4923:1997'); +INSERT INTO RHS VALUES(9,' RHS 70 x 30 x 4.0',70.0,30.0,4.0,5.45,6.95,37.23,9.42,2.31,1.16,10.64,6.28,14.2,7.66,'IS 4923:1997'); +INSERT INTO RHS VALUES(10,' RHS 80 x 40 x 2.9',80.0,40.0,2.9,5.03,6.41,50.87,17.11,2.82,1.63,12.72,8.56,16.07,9.88,'IS 4923:1997'); +INSERT INTO RHS VALUES(11,' RHS 80 x 40 x 3.2',80.0,40.0,3.2,5.5,7.01,54.94,18.41,2.8,1.62,13.74,9.21,17.46,10.72,'IS 4923:1997'); +INSERT INTO RHS VALUES(12,' RHS 80 x 40 x 4.0',80.0,40.0,4.0,6.71,8.55,64.79,21.49,2.75,1.59,16.2,10.74,20.91,12.77,'IS 4923:1997'); +INSERT INTO RHS VALUES(13,' RHS 96 x 48 x 3.2',96.0,48.0,3.2,6.71,8.54,98.61,33.28,3.4,1.97,20.54,13.87,25.85,15.91,'IS 4923:1997'); +INSERT INTO RHS VALUES(14,' RHS 96 x 48 x 4.0',96.0,48.0,4.0,8.22,10.47,117.54,39.32,3.55,1.94,24.49,16.3,31.21,19.14,'IS 4923:1997'); +INSERT INTO RHS VALUES(15,' RHS 96 x 48 x 4.8',96.0,48.0,4.8,9.66,12.31,134.35,44.55,3.3,1.9,27.99,18.56,36.13,22.08,'IS 4923:1997'); +INSERT INTO RHS VALUES(16,' RHS 100 x 50 x 3.2',100.0,50.0,3.2,7.01,8.93,112.29,37.95,3.55,2.06,22.46,15.18,28.2,17.37,'IS 4923:1997'); +INSERT INTO RHS VALUES(17,' RHS 100 x 50 x 4.0',100.0,50.0,4.0,8.59,10.95,134.14,44.95,3.5,2.03,26.83,17.98,34.1,20.93,'IS 4923:1997'); +INSERT INTO RHS VALUES(18,' RHS 122 x 61 x 3.6',122.0,61.0,3.6,9.67,12.32,232.61,78.83,4.34,2.35,38.13,25.84,47.71,29.42,'IS 4923:1997'); +INSERT INTO RHS VALUES(19,' RHS 122 x 61 x 4.5',122.0,61.0,4.5,11.88,15.14,278.94,93.78,4.29,2.49,45.73,30.75,57.85,35.56,'IS 4923:1997'); +INSERT INTO RHS VALUES(20,' RHS 122 x 61 x 5.4',122.0,61.0,5.4,14.01,17.85,320.83,107.03,4.24,2.45,52.6,35.09,67.29,41.22,'IS 4923:1997'); +INSERT INTO RHS VALUES(21,' RHS 127 x 50 x 3.6',127.0,50.0,3.6,9.34,11.89,227.08,52.05,4.37,2.09,35.76,20.82,45.95,23.7,'IS 4923:1997'); +INSERT INTO RHS VALUES(22,' RHS 127 x 50 x 4.6',127.0,50.0,4.6,11.69,14.89,276.33,62.46,4.31,2.05,43.52,24.98,56.66,29.04,'IS 4923:1997'); +INSERT INTO RHS VALUES(23,' RHS 145 x 82 x 4.8',145.0,82.0,4.8,15.92,20.28,555.16,228.5,5.23,3.36,76.57,55.73,94.93,63.93,'IS 4923:1997'); +INSERT INTO RHS VALUES(24,' RHS 145 x 82 x 5.4',145.0,82.0,5.4,17.74,22.6,610.85,250.59,5.2,3.33,84.26,61.12,105.07,70.66,'IS 4923:1997'); +INSERT INTO RHS VALUES(25,' RHS 172 x 92 x 4.8',172.0,92.0,4.8,18.71,23.83,917.13,346.91,6.2,3.82,106.64,75.41,132.08,85.61,'IS 4923:1997'); +INSERT INTO RHS VALUES(26,' RHS 172 x 92 x 5.4',172.0,92.0,5.4,20.88,26.59,1012.47,381.74,6.17,3.79,117.73,82.99,146.55,94.86,'IS 4923:1997'); +CREATE TABLE IF NOT EXISTS "CHS" ( + "Id" INTEGER, + "Designation" VARCHAR, + "NB" VARCHAR, + "OD" REAL(10 , 2), + "T" REAL(10 , 2), + "W" REAL(10 , 2), + "A" REAL(10 , 2), + "V" REAL(10 , 2), + "Ves" REAL(10 , 2), + "Vis" REAL(10 , 2), + "I" REAL(10 , 2), + "Z" REAL(10 , 2), + "R" REAL(10 , 2), + "Rsq" REAL(10 , 2), + "Source" VARCHAR, + PRIMARY KEY("Id") +); +INSERT INTO CHS VALUES(1,' CHS 21.3 x 2','15',21.3,2.0,0.95,1.21,235.0,669.0,543.0,0.57,0.54,0.69,0.47,'IS 1161:2014'); +INSERT INTO CHS VALUES(2,' CHS 21.3 x 2.6','15',21.3,2.6,1.2,1.53,204.0,669.0,506.0,0.68,0.64,0.67,0.45,'IS 1161:2014'); +INSERT INTO CHS VALUES(3,' CHS 21.3 x 3.2','15',21.3,3.2,1.43,1.82,174.0,669.0,468.0,0.77,0.72,0.65,0.42,'IS 1161:2014'); +INSERT INTO CHS VALUES(4,' CHS 26.9 x 2.3','20',26.9,2.3,1.4,1.78,391.0,845.0,701.0,1.36,1.01,0.87,0.76,'IS 1161:2014'); +INSERT INTO CHS VALUES(5,' CHS 26.9 x 2.6','20',26.9,2.6,1.56,1.98,370.0,845.0,682.0,1.48,1.1,0.86,0.75,'IS 1161:2014'); +INSERT INTO CHS VALUES(6,' CHS 26.9 x 3.2','20',26.9,3.2,1.87,2.38,330.0,845.0,644.0,1.7,1.27,0.85,0.71,'IS 1161:2014'); +INSERT INTO CHS VALUES(7,' CHS 33.7 x 2.6','25',33.7,2.6,1.99,2.54,638.0,1059.0,895.0,3.09,1.84,1.1,1.22,'IS 1161:2014'); +INSERT INTO CHS VALUES(8,' CHS 33.7 x 3.2','25',33.7,3.2,2.41,3.07,585.0,1059.0,858.0,3.6,2.14,1.08,1.18,'IS 1161:2014'); +INSERT INTO CHS VALUES(9,' CHS 33.7 x 4','25',33.7,4.0,2.93,3.73,519.0,1059.0,807.0,4.19,2.49,1.06,1.12,'IS 1161:2014'); +INSERT INTO CHS VALUES(10,' CHS 42.4 x 2.6','32',42.4,2.6,2.55,3.25,1087.0,1332.0,1169.0,6.46,3.05,1.41,1.99,'IS 1161:2014'); +INSERT INTO CHS VALUES(11,' CHS 42.4 x 3.2','32',42.4,3.2,3.09,3.94,1018.0,1332.0,1131.0,7.62,3.59,1.39,1.93,'IS 1161:2014'); +INSERT INTO CHS VALUES(12,' CHS 42.4 x 4','32',42.4,4.0,3.79,4.83,929.0,1332.0,1081.0,8.99,4.24,1.36,1.86,'IS 1161:2014'); +INSERT INTO CHS VALUES(13,' CHS 48.3 x 2.9','40',48.3,2.9,3.25,4.14,1419.0,1517.0,1335.0,10.7,4.43,1.61,2.59,'IS 1161:2014'); +INSERT INTO CHS VALUES(14,' CHS 48.3 x 3.2','40',48.3,3.2,3.56,4.53,1379.0,1517.0,1316.0,11.59,4.8,1.6,2.56,'IS 1161:2014'); +INSERT INTO CHS VALUES(15,' CHS 48.3 x 4','40',48.3,4.0,4.37,5.57,1276.0,1517.0,1266.0,13.77,5.7,1.57,2.47,'IS 1161:2014'); +INSERT INTO CHS VALUES(16,' CHS 60.3 x 2.9','50',60.3,2.9,4.11,5.23,2333.0,1894.0,1712.0,21.59,7.15,2.03,4.13,'IS 1161:2014'); +INSERT INTO CHS VALUES(17,' CHS 60.3 x 3.6','50',60.3,3.6,5.03,6.41,2215.0,1894.0,1668.0,25.87,8.58,2.01,4.03,'IS 1161:2014'); +INSERT INTO CHS VALUES(18,' CHS 60.3 x 4.5','50',60.3,4.5,6.19,7.89,2067.0,1894.0,1612.0,30.9,10.25,1.98,3.92,'IS 1161:2014'); +INSERT INTO CHS VALUES(19,' CHS 76.1 x 2.9','65',76.1,2.9,5.24,0.67,3882.0,2391.0,2209.0,44.74,11.76,2.59,6.71,'IS 1161:2014'); +INSERT INTO CHS VALUES(20,' CHS 76.1 x 3.6','65',76.1,3.6,6.44,8.2,3728.0,2391.0,2165.0,54.01,14.19,2.57,6.59,'IS 1161:2014'); +INSERT INTO CHS VALUES(21,' CHS 76.1 x 4.5','65',76.1,4.5,7.95,10.12,3536.0,2391.0,2108.0,65.12,17.11,2.54,6.43,'IS 1161:2014'); +INSERT INTO CHS VALUES(22,' CHS 88.9 x 3.2','80',88.9,3.2,6.76,8.62,5346.0,2793.0,2592.0,79.21,17.82,3.03,9.19,'IS 1161:2014'); +INSERT INTO CHS VALUES(23,' CHS 88.9 x 4','80',88.9,4.0,8.38,10.67,5140.0,2793.0,2542.0,95.34,21.67,3.0,9.03,'IS 1161:2014'); +INSERT INTO CHS VALUES(24,' CHS 88.9 x 4.8','80',88.9,4.8,9.96,12.68,4939.0,2793.0,2491.0,112.49,25.31,2.98,8.87,'IS 1161:2014'); +INSERT INTO CHS VALUES(25,' CHS 101.6 x 3.6','90',101.6,3.6,8.7,11.08,6999.0,3192.0,2966.0,133.24,26.23,3.47,12.02,'IS 1161:2014'); +INSERT INTO CHS VALUES(26,' CHS 101.6 x 4','90',101.6,4.0,9.63,12.26,6881.0,3192.0,2941.0,146.28,18.8,3.45,11.93,'IS 1161:2014'); +INSERT INTO CHS VALUES(27,' CHS 101.6 x 4.8','90',101.6,4.8,11.46,14.6,6648.0,3192.0,2890.0,171.39,33.74,3.43,11.74,'IS 1161:2014'); +INSERT INTO CHS VALUES(28,' CHS 114.3 x 3.6','100',114.3,3.6,9.83,12.52,9009.0,3591.0,3365.0,191.98,33.59,3.92,15.33,'IS 1161:2014'); +INSERT INTO CHS VALUES(29,' CHS 114.3 x 4.5','100',114.3,4.5,12.19,15.52,8709.0,3591.0,3308.0,234.32,41.0,3.89,15.1,'IS 1161:2014'); +INSERT INTO CHS VALUES(30,' CHS 114.3 x 5.4','100',114.3,5.4,14.5,18.47,8413.0,3591.0,3252.0,274.54,48.04,3.85,14.86,'IS 1161:2014'); +INSERT INTO CHS VALUES(31,' CHS 127 x 4.5','110',127.0,4.5,13.59,17.32,10936.0,3990.0,3707.0,325.29,51.23,4.33,18.78,'IS 1161:2014'); +INSERT INTO CHS VALUES(32,' CHS 127 x 4.8','110',127.0,4.8,14.47,18.43,10825.0,3990.0,3688.0,344.5,54.25,4.32,18.69,'IS 1161:2014'); +INSERT INTO CHS VALUES(33,' CHS 127 x 5.4','110',127.0,5.4,16.19,20.63,10605.0,3990.0,3651.0,382.04,60.16,4.3,18.52,'IS 1161:2014'); +INSERT INTO CHS VALUES(34,' CHS 139.7 x 4.5','125',139.7,4.5,15.0,19.11,13417.0,4389.0,4106.0,437.2,62.59,4.78,22.87,'IS 1161:2014'); +INSERT INTO CHS VALUES(35,' CHS 139.7 x 4.8','125',139.7,4.8,15.97,20.34,13295.0,4389.0,4087.0,463.33,66.33,4.77,22.78,'IS 1161:2014'); +INSERT INTO CHS VALUES(36,' CHS 139.7 x 5.4','125',139.7,5.4,17.89,22.78,13050.0,4389.0,4050.0,514.5,73.66,4.75,22.58,'IS 1161:2014'); +INSERT INTO CHS VALUES(37,' CHS 152.4 x 4.5','135',152.4,4.5,16.41,20.91,16151.0,4788.0,4505.0,572.24,75.1,5.23,27.37,'IS 1161:2014'); +INSERT INTO CHS VALUES(38,' CHS 152.4 x 4.8','135',152.4,4.8,17.47,22.26,16016.0,4788.0,4486.0,606.76,79.63,5.22,27.26,'IS 1161:2014'); +INSERT INTO CHS VALUES(39,' CHS 152.4 x 5.4','135',152.4,5.4,19.58,24.94,15748.0,4788.0,4448.0,674.51,88.52,5.2,27.05,'IS 1161:2014'); +INSERT INTO CHS VALUES(40,' CHS 165.1 x 4.5','150',165.1,4.5,17.82,22.7,19138.0,5187.0,4904.0,732.57,88.74,5.68,32.27,'IS 1161:2014'); +INSERT INTO CHS VALUES(41,' CHS 165.1 x 4.8','150',165.1,4.8,18.98,24.17,18991.0,5187.0,4885.0,777.13,94.14,5.67,32.15,'IS 1161:2014'); +INSERT INTO CHS VALUES(42,' CHS 165.1 x 5.4','150',165.1,5.4,21.27,27.09,18699.0,5187.0,4847.0,864.7,104.75,5.65,31.92,'IS 1161:2014'); +INSERT INTO CHS VALUES(43,' CHS 165.1 x 5.9','150',165.1,5.9,23.2,29.5,18465.0,5189.0,4818.0,970.0,113.4,5.63,31.72,'IS 1161:2014'); +INSERT INTO CHS VALUES(44,' CHS 165.1 x 6.3','150',165.1,6.3,24.67,31.43,18265.0,5187.0,4791.0,992.28,120.2,5.62,31.57,'IS 1161:2014'); +INSERT INTO CHS VALUES(45,' CHS 165.1 x 8','150',165.1,8.0,30.99,39.48,17460.0,5187.0,4684.0,1221.25,147.94,5.56,30.93,'IS 1161:2014'); +INSERT INTO CHS VALUES(46,' CHS 168.3 x 4.5','150',168.3,4.5,18.18,23.16,19931.0,5287.0,5005.0,777.22,92.36,5.79,33.56,'IS 1161:2014'); +INSERT INTO CHS VALUES(47,' CHS 168.3 x 4.8','150',168.3,4.8,19.35,24.66,19781.0,5287.0,4986.0,824.57,97.99,5.78,33.44,'IS 1161:2014'); +INSERT INTO CHS VALUES(48,' CHS 168.3 x 5.4','150',168.3,5.4,21.69,27.64,19483.0,5287.0,4948.0,917.69,109.05,5.76,33.21,'IS 1161:2014'); +INSERT INTO CHS VALUES(49,' CHS 168.3 x 6.3','150',168.3,6.3,25.17,32.06,19040.0,5287.0,4891.0,1053.42,125.18,5.73,32.85,'IS 1161:2014'); +INSERT INTO CHS VALUES(50,' CHS 168.3 x 8','150',168.3,8.0,31.63,40.29,18218.0,5287.0,4785.0,1297.27,154.16,5.67,32.2,'IS 1161:2014'); +INSERT INTO CHS VALUES(51,' CHS 168.3 x 10','150',168.3,10.0,39.04,49.73,17273.0,5287.0,4659.0,1563.98,185.86,5.61,31.45,'IS 1161:2014'); +INSERT INTO CHS VALUES(52,' CHS 193.7 x 4.8','175',193.7,4.8,22.36,28.49,26619.0,6085.0,5784.0,1271.39,131.27,6.68,44.63,'IS 1161:2014'); +INSERT INTO CHS VALUES(53,' CHS 193.7 x 5.4','175',193.7,5.4,25.08,31.94,26273.0,6085.0,5746.0,1416.97,146.31,6.66,44.36,'IS 1161:2014'); +INSERT INTO CHS VALUES(54,' CHS 193.7 x 5.9','175',193.7,5.9,27.33,34.81,25987.0,6085.0,5715.0,1536.13,158.61,6.64,44.13,'IS 1161:2014'); +INSERT INTO CHS VALUES(55,' CHS 193.7 x 6.3','175',193.7,6.3,29.12,37.09,25759.0,6085.0,5689.0,1630.05,168.31,6.63,43.95,'IS 1161:2014'); +INSERT INTO CHS VALUES(56,' CHS 193.7 x 8','175',193.7,8.0,36.64,46.67,24801.0,6085.0,5583.0,2015.54,208.11,6.57,43.19,'IS 1161:2014'); +INSERT INTO CHS VALUES(57,' CHS 193.7 x 10','175',193.7,10.0,45.3,57.71,23697.0,6085.0,5457.0,2441.59,252.1,6.5,42.31,'IS 1161:2014'); +INSERT INTO CHS VALUES(58,' CHS 193.7 x 12','175',193.7,12.0,53.77,68.5,22618.0,6085.0,5331.0,2839.2,293.15,6.44,41.45,'IS 1161:2014'); +INSERT INTO CHS VALUES(59,' CHS 219.1 x 4.8','200',219.1,4.8,25.37,32.32,34471.0,6883.0,6582.0,1856.03,169.42,7.58,57.43,'IS 1161:2014'); +INSERT INTO CHS VALUES(60,' CHS 219.1 x 5.6','200',219.1,5.6,29.49,37.56,33947.0,6883.0,6531.0,2141.61,195.49,7.55,57.02,'IS 1161:2014'); +INSERT INTO CHS VALUES(61,' CHS 219.1 x 5.9','200',219.1,5.9,31.02,39.52,33751.0,6883.0,6513.0,2247.01,205.11,7.54,56.86,'IS 1161:2014'); +INSERT INTO CHS VALUES(62,' CHS 219.1 x 6.3','200',219.1,6.3,33.06,42.12,33491.0,6883.0,6487.0,2386.14,217.81,7.53,56.65,'IS 1161:2014'); +INSERT INTO CHS VALUES(63,' CHS 219.1 x 8','200',219.1,8.0,41.65,53.06,32397.0,6883.0,6381.0,2959.63,'270.I6',7.47,55.78,'IS 1161:2014'); +INSERT INTO CHS VALUES(64,' CHS 219.1 x 10','200',219.1,10.0,51.57,65.69,31134.0,6883.0,6255.0,3598.44,328.47,7.4,54.78,'IS 1161:2014'); +INSERT INTO CHS VALUES(65,' CHS 219.1 x 12','200',219.1,12.0,61.29,78.07,29895.0,6883.0,6129.0,4199.88,383.38,7.33,53.79,'IS 1161:2014'); +INSERT INTO CHS VALUES(66,' CHS 244.5 x 5.9','225',244.5,5.9,34.72,44.23,42529.0,7681.0,7310.0,3149.12,257.6,8.44,71.21,'IS 1161:2014'); +INSERT INTO CHS VALUES(67,' CHS 244.5 x 6.3','225',244.5,6.3,37.01,47.14,42237.0,7681.0,7285.0,3346.03,273.7,8.42,70.97,'IS 1161:2014'); +INSERT INTO CHS VALUES(68,' CHS 244.5 x 8','225',244.5,8.0,46.66,59.44,41007.0,7681.0,7179.0,4160.45,340.32,8.37,70.0,'IS 1161:2014'); +INSERT INTO CHS VALUES(69,' CHS 244.5 x 10','225',244.5,10.0,57.83,73.67,39584.0,7681.0,7053.0,5073.15,414.98,8.3,68.86,'IS 1161:2014'); +INSERT INTO CHS VALUES(70,' CHS 273 x 5.9','250',273.0,5.9,38.86,49.51,53584.0,8577.0,8206.0,4417.18,323.6,9.45,89.22,'IS 1161:2014'); +INSERT INTO CHS VALUES(71,' CHS 273 x 6.3','250',273.0,6.3,41.44,52.79,53256.0,8577.0,8181.0,4695.82,344.02,9.43,88.96,'IS 1161:2014'); +INSERT INTO CHS VALUES(72,' CHS 273 x 8','250',273.0,8.0,52.28,66.6,51875.0,8577.0,8074.0,5851.71,428.7,9.37,87.86,'IS 1161:2014'); +INSERT INTO CHS VALUES(73,' CHS 273 x 10','250',273.0,10.0,64.86,82.62,50273.0,8577.0,7948.0,7154.09,524.11,9.31,86.59,'IS 1161:2014'); +INSERT INTO CHS VALUES(74,' CHS 273 x 12','250',273.0,12.0,77.24,98.39,48695.0,8577.0,7823.0,8396.14,615.1,9.24,85.33,'IS 1161:2014'); +INSERT INTO CHS VALUES(75,' CHS 323.9 x 6.3','300',323.9,6.3,49.34,62.86,76111.0,10176.0,9780.0,7928.9,489.59,11.23,126.14,'IS 1161:2014'); +INSERT INTO CHS VALUES(76,' CHS 323.9 x 8','300',323.9,8.0,62.32,79.39,74458.0,10176.0,9673.0,9910.08,611.92,11.17,124.82,'IS 1161:2014'); +INSERT INTO CHS VALUES(77,' CHS 323.9 x 10','300',323.9,10.0,77.41,98.61,72536.0,10176.0,9547.0,12158.34,750.75,11.1,123.29,'IS 1161:2014'); +INSERT INTO CHS VALUES(78,' CHS 323.9 x 12','300',323.9,12.0,92.3,117.58,70639.0,10176.0,9422.0,14319.56,884.2,11.04,121.78,'IS 1161:2014'); +INSERT INTO CHS VALUES(79,' CHS 355.6 x 8','350',355.6,8.0,68.58,87.36,90579.0,11172.0,10669.0,13201.37,742.48,12.29,151.11,'IS 1161:2014'); +INSERT INTO CHS VALUES(80,' CHS 355.6 x 10','350',355.6,10.0,85.23,108.57,88457.0,11172.0,10543.0,16223.5,912.46,12.22,149.42,'IS 1161:2014'); +INSERT INTO CHS VALUES(81,' CHS 355.6 x 12','350',355.6,12.0,101.68,129.53,86361.0,11172.0,10418.0,19139.47,1076.46,12.16,147.76,'IS 1161:2014'); +CREATE TABLE IF NOT EXISTS "Angles" ( + "Id" INTEGER, + "Designation" VARCHAR(50), + "Mass" REAL(10 , 2), + "Area" REAL(10 , 2), + "a" REAL(10 , 2), + "b" REAL(10 , 2), + "t" REAL(10 , 2), + "R1" REAL(10 , 2), + "R2" REAL(10 , 2) DEFAULT (null), + "Cz" REAL(10 , 2), + "Cy" REAL(10 , 2), + "Iz" REAL(10 , 2), + "Iy" REAL(10 , 2), + "Alpha" REAL(10 , 2), + "Iumax" REAL(10 , 2), + "Ivmin" REAL(10 , 2), + "rz" REAL(10 , 2), + "ry" REAL(10 , 2), + "rumax" REAL(10 , 2), + "rvmin" REAL(10 , 2), + "Zz" REAL(10 , 2), + "Zy" REAL(10 , 2), + "Zpz" REAL(10 , 2), + "Zpy" REAL(10 , 2), + "It" REAL(10 , 2), + "Source" VARCHAR(100), + "Type" VARCHAR(100), + PRIMARY KEY("Id") +); +INSERT INTO Angles VALUES(1,'20 x 20 x 3',0.9,1.14,20.0,20.0,3.0,4.0,0.0,0.6,0.6,0.4,0.4,0.79,0.64,0.17,0.59,0.59,0.75,0.39,0.29,0.29,0.52,0.53,0.033000000000000007105,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(2,'20 x 20 x 4',1.16,1.47,20.0,20.0,4.0,4.0,0.0,0.64,0.64,0.5,0.5,0.79,0.79,0.22,0.58,0.58,0.73,0.39,0.37,0.37,0.66,0.67,0.075999999999999996447,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(3,'25 x 25 x 3',1.14,1.45,25.0,25.0,3.0,4.5,0.0,0.73,0.73,0.83,0.83,0.79,1.3,0.35,0.75,0.75,0.95,0.49,0.46,0.46,0.83,0.84,0.042000000000000001776,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(4,'25 x 25 x 4',1.48,1.88,25.0,25.0,4.0,4.5,0.0,0.76,0.76,1.04,1.04,0.79,1.63,0.44,0.74,0.74,0.93,0.48,0.6,0.6,1.07,1.09,0.098000000000000024868,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(5,'25 x 25 x 5',1.8,2.29,25.0,25.0,5.0,4.5,0.0,0.8,0.8,1.23,1.23,0.79,1.92,0.54,0.73,0.73,0.92,0.48,0.72,0.72,1.3,1.31,0.18700000000000001065,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(6,'30 x 30 x 3',1.38,1.76,30.0,30.0,3.0,5.0,0.0,0.85,0.85,1.47,1.47,0.79,2.32,0.62,0.91,0.91,1.15,0.59,0.68,0.68,1.22,1.23,0.050999999999999996447,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(7,'30 x 30 x 4',1.8,2.29,30.0,30.0,4.0,5.0,0.0,0.89,0.89,1.86,1.86,0.79,2.94,0.78,0.9,0.9,1.13,0.58,0.88,0.88,1.58,1.6,0.11899999999999999467,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(8,'30 x 30 x 5',2.2,2.8,30.0,30.0,5.0,5.0,0.0,0.93,0.93,2.22,2.22,0.79,3.49,0.95,0.89,0.89,1.12,0.58,1.07,1.07,1.92,1.94,0.22900000000000000355,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(9,'35 x 35 x 3',1.62,2.06,35.0,35.0,3.0,5.0,0.0,0.97,0.97,2.38,2.38,0.79,3.77,0.99,1.07,1.07,1.35,0.69,0.94,0.94,1.69,1.7,0.06,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(10,'35 x 35 x 4',2.11,2.69,35.0,35.0,4.0,5.0,0.0,1.01,1.01,3.04,3.04,0.79,4.81,1.27,1.06,1.06,1.34,0.69,1.22,1.22,2.19,2.21,0.14,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(11,'35 x 35 x 5',2.59,3.3,35.0,35.0,5.0,5.0,0.0,1.05,1.05,3.65,3.65,0.79,5.76,1.54,1.05,1.05,1.32,0.68,1.49,1.49,2.68,2.69,0.27,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(12,'35 x 35 x 6',3.06,3.89,35.0,35.0,6.0,5.0,0.0,1.09,1.09,4.2,4.2,0.79,6.61,1.8,1.04,1.04,1.3,0.68,1.74,1.74,3.14,3.15,0.46,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(13,'40 x 40 x 3',1.86,2.37,40.0,40.0,3.0,5.5,0.0,1.09,1.09,3.61,3.61,0.79,5.72,1.51,1.23,1.23,1.55,0.8,1.24,1.24,2.22,2.24,0.069000000000000003552,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(14,'40 x 40 x 4',2.44,3.1,40.0,40.0,4.0,5.5,0.0,1.13,1.13,4.63,4.63,0.79,7.34,1.93,1.22,1.22,1.54,0.79,1.62,1.62,2.9,2.92,0.16200000000000001065,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(15,'40 x 40 x 5',2.99,3.81,40.0,40.0,5.0,5.5,0.0,1.17,1.17,5.58,5.58,0.79,8.83,2.33,1.21,1.21,1.52,0.78,1.97,1.97,3.55,3.57,0.31200000000000001065,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(16,'40 x 40 x 6',3.54,4.5,40.0,40.0,6.0,5.5,0.0,1.21,1.21,6.46,6.46,0.79,10.2,2.73,1.2,1.2,1.5,0.78,2.32,2.32,4.17,4.19,0.53200000000000002842,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(17,'45 x 45 x 3',2.1,2.67,45.0,45.0,3.0,5.5,0.0,1.22,1.22,5.2,5.2,0.79,8.2,2.17,1.39,1.39,1.76,0.9,1.58,1.58,2.84,2.86,0.078000000000000015987,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(18,'45 x 45 x 4',2.75,3.5,45.0,45.0,4.0,5.5,0.0,1.26,1.26,6.7,6.7,0.79,10.6,2.78,1.38,1.38,1.74,0.89,2.07,2.07,3.71,3.73,0.1830000000000000071,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(19,'45 x 45 x 5',3.39,4.31,45.0,45.0,5.0,5.5,0.0,1.3,1.3,8.1,8.1,0.79,12.8,3.37,1.37,1.37,1.72,0.88,2.53,2.53,4.55,4.57,0.35400000000000000355,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(20,'45 x 45 x 6',4.01,5.1,45.0,45.0,6.0,5.5,0.0,1.34,1.34,9.42,9.42,0.79,14.9,3.94,1.36,1.36,1.71,0.88,2.98,2.98,5.36,5.38,0.60400000000000000355,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(21,'50 x 50 x 3',2.34,2.99,50.0,50.0,3.0,6.0,0.0,1.34,1.34,7.21,7.21,0.79,11.4,3.01,1.55,1.55,1.96,1.0,1.97,1.97,3.53,3.55,0.086999999999999992894,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(22,'50 x 50 x 4',3.08,3.92,50.0,50.0,4.0,6.0,0.0,1.38,1.38,9.32,9.32,0.79,14.8,3.86,1.54,1.54,1.94,0.99,2.57,2.57,4.62,4.64,0.20400000000000000355,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(23,'50 x 50 x 5',3.79,4.83,50.0,50.0,5.0,6.0,0.0,1.42,1.42,11.3,11.3,0.79,17.9,4.69,1.53,1.53,1.93,0.99,3.16,3.16,5.67,5.7,0.39500000000000001776,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(24,'50 x 50 x 6',4.49,5.72,50.0,50.0,6.0,6.0,0.0,1.46,1.46,13.2,13.2,0.79,20.8,5.48,1.52,1.52,1.91,0.98,3.72,3.72,6.69,6.71,0.67600000000000015631,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(25,'55 x 55 x 4',3.4,4.33,55.0,55.0,4.0,6.5,0.0,1.5,1.5,12.5,12.5,0.79,19.9,5.2,1.7,1.7,2.14,1.1,3.14,3.14,5.63,5.66,0.22600000000000002309,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(26,'55 x 55 x 5',4.19,5.34,55.0,55.0,5.0,6.5,0.0,1.54,1.54,15.2,15.2,0.79,24.2,6.31,1.69,1.69,2.13,1.09,3.85,3.85,6.92,6.95,0.43700000000000001065,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(27,'55 x 55 x 6',4.97,6.33,55.0,55.0,6.0,6.5,0.0,1.58,1.58,17.8,17.8,0.79,28.2,7.39,1.68,1.68,2.11,1.08,4.55,4.55,8.17,8.2,0.74800000000000004263,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(28,'55 x 55 x 8',6.48,8.25,55.0,55.0,8.0,6.5,0.0,1.66,1.66,22.5,22.5,0.79,35.6,9.48,1.65,1.65,2.08,1.07,5.87,5.87,10.5,10.6,1.74,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(29,'60 x 60 x 4',3.71,4.73,60.0,60.0,4.0,6.5,0.0,1.63,1.63,16.4,16.4,0.79,26.0,6.8,1.86,1.86,2.35,1.2,3.76,3.76,6.74,6.77,0.24699999999999993072,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(30,'60 x 60 x 5',4.58,5.84,60.0,60.0,5.0,6.5,0.0,1.67,1.67,20.0,20.0,0.79,31.7,8.26,1.85,1.85,2.33,1.19,4.62,4.62,8.3,8.32,0.47900000000000000355,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(31,'60 x 60 x 6',5.44,6.93,60.0,60.0,6.0,6.5,0.0,1.71,1.71,23.4,23.4,0.79,37.1,9.69,1.84,1.84,2.31,1.18,5.46,5.46,9.81,9.84,0.82,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(32,'60 x 60 x 8',7.1,9.05,60.0,60.0,8.0,6.5,0.0,1.78,1.78,29.8,29.8,0.79,47.1,12.4,1.81,1.81,2.28,1.17,7.06,7.06,12.7,12.7,1.91,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(33,'65 x 65 x 4',4.03,5.13,65.0,65.0,4.0,6.5,0.0,1.75,1.75,21.0,21.0,0.79,33.4,8.69,2.02,2.02,2.55,1.3,4.43,4.43,7.95,7.98,0.26800000000000001598,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(34,'65 x 65 x 5',4.98,6.34,65.0,65.0,5.0,6.5,0.0,1.79,1.79,25.7,25.7,0.79,40.8,10.6,2.01,2.01,2.54,1.29,5.45,5.45,9.8,9.83,0.52,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(35,'65 x 65 x 6',5.91,7.53,65.0,65.0,6.0,6.5,0.0,1.83,1.83,30.1,30.1,0.79,47.8,12.4,2.0,2.0,2.52,1.28,6.45,6.45,11.5,11.6,0.89199999999999999289,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(36,'65 x 65 x 8',7.73,9.85,65.0,65.0,8.0,6.5,0.0,1.91,1.91,38.4,38.4,0.79,60.8,16.0,1.97,1.97,2.48,1.27,8.36,8.36,15.0,15.0,2.08,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(37,'70 x 70 x 5',5.38,6.86,70.0,70.0,5.0,7.0,0.0,1.92,1.92,32.3,32.3,0.79,51.3,13.3,2.17,2.17,2.74,1.39,6.36,6.36,11.4,11.4,0.56200000000000009947,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(38,'70 x 70 x 6',6.39,8.15,70.0,70.0,6.0,7.0,0.0,1.96,1.96,38.0,38.0,0.79,60.3,15.6,2.16,2.16,2.72,1.39,7.53,7.53,13.5,13.5,0.96400000000000005684,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(39,'70 x 70 x 8',8.37,10.6,70.0,70.0,8.0,7.0,0.0,2.03,2.03,48.5,48.5,0.79,76.9,20.1,2.13,2.13,2.69,1.37,9.77,9.77,17.5,17.6,2.25,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(40,'70 x 70 x 10',10.29,13.1,70.0,70.0,10.0,7.0,0.0,2.11,2.11,58.3,58.3,0.79,92.1,24.4,2.11,2.11,2.65,1.37,11.9,11.9,21.4,21.5,4.33,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(41,'75 x 75 x 5',5.77,7.36,75.0,75.0,5.0,7.0,0.0,2.04,2.04,40.0,40.0,0.79,63.6,16.5,2.33,2.33,2.94,1.5,7.3,7.3,13.2,13.2,0.60400000000000000355,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(42,'75 x 75 x 6',6.86,8.75,75.0,75.0,6.0,7.0,0.0,2.08,2.08,47.1,47.1,0.79,74.8,19.4,2.32,2.32,2.92,1.49,8.7,8.7,15.6,15.6,1.03,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(43,'75 x 75 x 8',9.0,11.4,75.0,75.0,8.0,7.0,0.0,2.16,2.16,60.3,60.3,0.79,95.7,24.9,2.29,2.29,2.89,1.47,11.3,11.3,20.3,20.4,2.42,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(44,'75 x 75 x 10',11.07,14.1,75.0,75.0,10.0,7.0,0.0,2.23,2.23,72.6,72.6,0.79,114.0,30.3,2.27,2.27,2.85,1.47,13.8,13.8,24.8,24.9,4.66,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(45,'80 x 80 x 6',7.36,9.38,80.0,80.0,6.0,8.0,0.0,2.2,2.2,57.6,57.6,0.79,91.4,23.7,2.48,2.48,3.12,1.59,9.9,9.9,17.8,17.9,1.1,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(46,'80 x 80 x 8',9.65,12.3,80.0,80.0,8.0,8.0,0.0,2.28,2.28,74.0,74.0,0.79,117.0,30.5,2.45,2.45,3.09,1.58,12.9,12.9,23.3,23.3,2.59,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(47,'80 x 80 x 10',11.88,15.1,80.0,80.0,10.0,8.0,0.0,2.36,2.36,89.2,89.2,0.79,141.0,37.1,2.43,2.43,3.05,1.57,15.8,15.8,28.4,28.5,5.0,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(48,'80 x 80 x 12',14.05,17.9,80.0,80.0,12.0,8.0,0.0,2.43,2.43,103.0,103.0,0.79,163.0,43.5,2.4,2.4,3.02,1.56,18.5,18.5,33.4,33.5,8.52,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(49,'90 x 90 x 6',8.32,10.6,90.0,90.0,6.0,8.5,0.0,2.45,2.45,83.0,83.0,0.79,131.0,34.2,2.8,2.8,3.53,1.8,12.7,12.7,22.8,22.8,1.25,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(50,'90 x 90 x 8',10.92,13.9,90.0,90.0,8.0,8.5,0.0,2.53,2.53,107.0,107.0,0.79,170.0,44.1,2.77,2.77,3.5,1.78,16.5,16.5,29.7,29.8,2.93,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(51,'90 x 90 x 10',13.47,17.1,90.0,90.0,10.0,8.5,0.0,2.6,2.6,129.0,129.0,0.79,205.0,53.6,2.75,2.75,3.46,1.77,20.2,20.2,36.4,36.5,5.66,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(52,'90 x 90 x 12',15.95,20.3,90.0,90.0,12.0,8.5,0.0,2.68,2.68,150.0,150.0,0.79,238.0,62.8,2.72,2.72,3.42,1.76,23.8,23.8,42.9,43.0,9.67,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(53,'100 x 100 x 6',9.26,11.8,100.0,100.0,6.0,8.5,0.0,2.7,2.7,115.0,115.0,0.79,182.0,47.2,3.12,3.12,3.94,2.0,15.7,15.7,28.3,28.3,1.39,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(54,'100 x 100 x 8',12.18,15.5,100.0,100.0,8.0,8.5,0.0,2.78,2.78,148.0,148.0,0.79,236.0,61.0,3.1,3.1,3.9,1.98,20.6,20.6,37.0,37.1,3.27,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(55,'100 x 100 x 10',15.04,19.1,100.0,100.0,10.0,8.5,0.0,2.85,2.85,180.0,180.0,0.79,286.0,74.3,3.07,3.07,3.87,1.97,25.3,25.3,45.4,45.5,6.33,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(56,'100 x 100 x 12',17.83,22.7,100.0,100.0,12.0,8.5,0.0,2.93,2.93,210.0,210.0,0.79,333.0,87.2,3.04,3.04,3.83,1.96,29.8,29.8,53.6,53.7,10.8,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(57,'110 x 110 x 8',13.4,17.0,110.0,110.0,8.0,10.0,4.8,3.0,3.0,196.0,196.0,0.79,312.0,80.7,3.39,3.39,4.28,2.17,24.6,24.6,44.6,44.7,3.61,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(58,'110 x 110 x 10',16.58,21.1,110.0,110.0,10.0,10.0,4.8,3.09,3.09,240.0,240.0,0.79,381.0,98.6,3.37,3.37,4.25,2.16,30.4,30.4,54.9,55.0,7.0,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(59,'110 x 110 x 12',19.68,25.0,110.0,110.0,12.0,10.0,4.8,3.17,3.17,281.0,281.0,0.79,446.0,116.0,3.35,3.35,4.22,2.15,35.9,35.9,64.9,65.1,11.9,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(60,'110 x 110 x 16',25.71,32.7,110.0,110.0,16.0,10.0,4.8,3.32,3.32,357.0,357.0,0.79,565.0,149.0,3.3,3.3,4.15,2.14,46.5,46.5,84.1,84.2,27.8,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(61,'130 x 130 x 8',15.92,20.2,130.0,130.0,8.0,10.0,4.8,3.5,3.5,330.0,330.0,0.79,526.0,135.0,4.04,4.04,5.1,2.58,34.8,34.8,63.0,63.1,4.3,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(62,'130 x130 x 10',19.72,25.1,130.0,130.0,10.0,10.0,4.8,3.59,3.59,405.0,405.0,0.79,644.0,165.0,4.02,4.02,5.07,2.57,43.1,43.1,77.8,77.9,8.33,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(63,'130 x130 x 12',23.45,29.8,130.0,130.0,12.0,10.0,4.8,3.67,3.67,476.0,476.0,0.79,757.0,195.0,3.99,3.99,5.04,2.56,51.0,51.0,92.2,92.3,14.2,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(64,'130 x130 x 16',30.74,39.1,130.0,130.0,16.0,10.0,4.8,3.82,3.82,609.0,609.0,0.79,966.0,252.0,3.94,3.94,4.97,2.54,66.3,66.3,119.0,120.0,33.3,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(65,'150 x 150 x 10',22.93,29.2,150.0,150.0,10.0,12.0,4.8,4.08,4.08,633.0,633.0,0.79,1000.0,259.0,4.66,4.66,5.87,2.98,58.0,58.0,104.0,104.0,9.66,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(66,'150 x 150 x 12',27.29,34.7,150.0,150.0,12.0,12.0,4.8,4.16,4.16,746.0,746.0,0.79,1180.0,305.0,4.63,4.63,5.84,2.96,68.8,68.8,124.0,124.0,16.5,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(67,'150 x 150 x 16',35.84,45.6,150.0,150.0,16.0,12.0,4.8,4.31,4.31,958.0,958.0,0.79,1520.0,394.0,4.58,4.58,5.78,2.94,89.7,89.7,162.0,162.0,38.7,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(68,'150 x 150 x 20',44.12,56.2,150.0,150.0,20.0,12.0,4.8,4.46,4.46,1150.0,1150.0,0.79,1830.0,480.0,4.53,4.53,5.71,2.92,109.0,109.0,198.0,198.0,74.6,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(69,'200 x 200 x 12',36.85,46.9,200.0,200.0,12.0,15.0,4.8,5.39,5.39,1820.0,1820.0,0.79,2900.0,746.0,6.24,6.24,7.87,3.99,125.0,125.0,225.0,225.0,22.3,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(70,'200 x 200 x 16',48.53,61.8,200.0,200.0,16.0,15.0,4.8,5.56,5.56,2360.0,2360.0,0.79,3760.0,967.0,6.19,6.19,7.8,3.96,163.0,163.0,295.0,295.0,52.4,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(71,'200 x 200 x 20',59.96,76.3,200.0,200.0,20.0,15.0,4.8,5.71,5.71,2870.0,2870.0,0.79,4560.0,1180.0,6.13,6.13,7.73,3.93,201.0,201.0,362.0,363.0,101.0,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(72,'200 x 200 x 25',73.9,94.1,200.0,200.0,25.0,15.0,4.8,5.9,5.9,3470.0,3470.0,0.79,5500.0,1430.0,6.07,6.07,7.65,3.91,246.0,246.0,443.0,444.0,195.0,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(73,'50 x 50 x 7',5.17,6.59,50.0,50.0,7.0,6.0,0.0,1.5,1.5,14.9,14.9,0.79,23.6,6.27,1.51,1.51,1.89,0.98,4.26,4.26,7.67,7.7,1.06,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(74,'50 x 50 x 8',5.84,7.44,50.0,50.0,8.0,6.0,0.0,1.54,1.54,16.6,16.6,0.79,26.2,7.03,1.49,1.49,1.88,0.97,4.79,4.79,8.62,8.65,1.57,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(75,'55 x 55 x 10',7.92,10.0,55.0,55.0,10.0,6.5,0.0,1.73,1.73,26.8,26.8,0.79,42.1,11.5,1.63,1.63,2.04,1.07,7.11,7.11,12.8,12.8,3.33,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(76,'60 x 60 x 10',8.71,11.0,60.0,60.0,10.0,6.5,0.0,1.86,1.86,35.5,35.5,0.79,55.9,15.1,1.79,1.79,2.25,1.17,8.57,8.57,15.4,15.4,3.66,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(77,'65 x 65 x 10',9.49,12.0,65.0,65.0,10.0,6.5,0.0,1.98,1.98,45.9,45.9,0.79,72.5,19.4,1.95,1.95,2.45,1.27,10.1,10.1,18.3,18.3,4.0,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(78,'70 x 70 x 7',7.39,9.42,70.0,70.0,7.0,7.0,0.0,2.0,2.0,43.4,43.4,0.79,68.8,17.9,2.15,2.15,2.7,1.38,8.66,8.66,15.5,15.6,1.52,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(79,'100 x 100 x 7',10.73,13.6,100.0,100.0,7.0,8.5,0.0,2.74,2.74,132.0,132.0,0.79,210.0,54.2,3.11,3.11,3.92,1.99,18.2,18.2,32.7,32.7,2.2,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(80,'100 x 100 x 15',21.91,27.9,100.0,100.0,15.0,8.5,0.0,3.04,3.04,252.0,252.0,0.79,398.0,106.0,3.01,3.01,3.78,1.95,36.2,36.2,65.3,65.4,20.8,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(81,'120 x 120 x 8',14.66,18.6,120.0,120.0,8.0,10.0,4.8,3.25,3.25,258.0,258.0,0.79,410.0,105.0,3.72,3.72,4.69,2.38,29.5,29.5,53.4,53.5,3.95,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(82,'120 x 120 x 10',18.15,23.1,120.0,120.0,10.0,10.0,4.8,3.34,3.34,315.0,315.0,0.79,501.0,129.0,3.69,3.69,4.66,2.36,36.4,36.4,65.9,66.0,7.66,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(83,'120 x 120 x 12',21.57,27.4,120.0,120.0,12.0,10.0,4.8,3.42,3.42,370.0,370.0,0.79,588.0,152.0,3.67,3.67,4.63,2.35,43.1,43.1,78.0,78.1,13.1,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(84,'120 x 120 x 15',26.58,33.8,120.0,120.0,15.0,10.0,4.8,3.53,3.53,447.0,447.0,0.79,709.0,185.0,3.64,3.64,4.58,2.34,52.8,52.8,95.5,95.6,25.3,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(85,'130 x 130 x 9',17.82,22.7,130.0,130.0,9.0,10.0,4.8,3.55,3.55,368.0,368.0,0.79,586.0,150.0,4.03,4.03,5.08,2.57,39.0,39.0,70.5,70.6,6.09,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(86,'150 x 150 x 15',33.72,42.9,150.0,150.0,15.0,12.0,4.8,4.28,4.28,907.0,907.0,0.79,1440.0,372.0,4.6,4.6,5.79,2.95,84.6,84.6,152.0,152.0,32.0,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(87,'150 x 150 x 18',40.01,50.9,150.0,150.0,18.0,12.0,4.8,4.39,4.39,1050.0,1050.0,0.79,1680.0,437.0,4.56,4.56,5.74,2.93,99.8,99.8,180.0,180.0,54.8,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(88,'180 x 180 x 15',41.09,52.3,180.0,180.0,15.0,18.0,4.8,5.0,5.0,1610.0,1610.0,0.79,2550.0,663.0,5.55,5.55,6.99,3.56,123.0,123.0,223.0,223.0,38.8,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(89,'180 x 180 x 18',48.79,62.1,180.0,180.0,18.0,18.0,4.8,5.12,5.12,1880.0,1880.0,0.79,2990.0,778.0,5.51,5.51,6.94,3.54,146.0,146.0,264.0,264.0,66.4,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(90,'180 x 180 x 20',53.85,68.6,180.0,180.0,20.0,18.0,4.8,5.2,5.2,2060.0,2060.0,0.79,3270.0,853.0,5.49,5.49,6.91,3.53,161.0,161.0,290.0,291.0,90.6,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(91,'200 x 200 x 24',71.31,90.8,200.0,200.0,24.0,18.0,4.8,5.85,5.85,3350.0,3350.0,0.79,5320.0,1390.0,6.08,6.08,7.65,3.91,237.0,237.0,427.0,428.0,173.0,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(92,'30 x 20 x 3',1.14,1.45,30.0,20.0,3.0,4.5,0.0,0.99,0.51,1.29,0.46,1.05,1.47,0.27,0.94,0.56,1.01,0.43,0.64,0.31,1.16,0.56,0.042000000000000001776,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(93,'30 x 20 x 4',1.48,1.88,30.0,20.0,4.0,4.5,0.0,1.04,0.55,1.63,0.57,0.4,1.85,0.34,0.93,0.55,0.99,0.43,0.83,0.39,1.48,0.73,0.098000000000000024868,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(94,'30 x 20 x 5',1.8,2.29,30.0,20.0,5.0,4.5,0.0,1.07,0.58,1.93,0.67,0.39,2.19,0.41,0.92,0.54,0.98,0.42,1.0,0.47,1.79,0.9,0.18700000000000001065,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(95,'40 x 25 x 3',1.5,1.91,40.0,25.0,3.0,5.0,0.0,1.32,0.59,3.11,0.94,0.37,3.48,0.57,1.27,0.7,1.35,0.54,1.16,0.49,2.08,0.9,0.055,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(96,'40 x 25 x 4',1.96,2.49,40.0,25.0,4.0,5.0,0.0,1.36,0.63,3.97,1.19,0.36,4.44,0.72,1.26,0.69,1.33,0.54,1.5,0.64,2.69,1.18,0.13,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(97,'40 x 25 x 5',2.4,3.05,40.0,25.0,5.0,5.0,0.0,1.4,0.67,4.76,1.42,0.36,5.31,0.87,1.25,0.68,1.32,0.53,1.83,0.77,3.27,1.45,0.25,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(98,'40 x 25 x 6',2.82,3.59,40.0,25.0,6.0,5.0,0.0,1.44,0.7,5.5,1.62,0.35,6.1,1.02,1.24,0.67,1.3,0.53,2.15,0.9,3.81,1.72,0.42400000000000002131,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(99,'45 x 30 x 3',1.74,2.21,45.0,30.0,3.0,5.0,0.0,1.44,0.71,4.57,1.65,0.41,5.26,0.96,1.44,0.86,1.54,0.66,1.49,0.72,2.7,1.29,0.064000000000000003552,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(100,'45 x 30 x 4',2.27,2.89,45.0,30.0,4.0,5.0,0.0,1.48,0.74,5.87,2.1,0.41,6.75,1.22,1.42,0.85,1.53,0.65,1.95,0.93,3.5,1.69,0.15099999999999999644,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(101,'45 x 30 x 5',2.79,3.55,45.0,30.0,5.0,5.0,0.0,1.52,0.78,7.08,2.51,0.4,8.11,1.48,1.41,0.84,1.51,0.64,2.38,1.13,4.27,2.08,0.29099999999999997868,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(102,'45 x 30 x 6',3.29,4.19,45.0,30.0,6.0,5.0,0.0,1.56,0.82,8.21,2.89,0.4,9.37,1.73,1.4,0.83,1.49,0.64,2.79,1.32,5.0,2.46,0.49599999999999999644,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(103,'50 x 30 x 3',1.86,2.37,50.0,30.0,3.0,5.5,0.0,1.64,0.67,6.13,1.69,0.35,6.79,1.03,1.61,0.84,1.69,0.66,1.83,0.73,3.28,1.31,0.069000000000000003552,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(104,'50 x 30 x 4',2.44,3.1,50.0,30.0,4.0,5.5,0.0,1.69,0.71,7.89,2.15,0.34,8.73,1.32,1.59,0.83,1.68,0.65,2.38,0.94,4.26,1.72,0.16200000000000001065,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(105,'50 x 30 x 5',2.99,3.81,50.0,30.0,5.0,5.5,0.0,1.73,0.75,9.53,2.58,0.34,10.5,1.59,1.58,0.82,1.66,0.65,2.92,1.15,5.19,2.13,0.31200000000000001065,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(106,'50 x 30 x 6',3.54,4.5,50.0,30.0,6.0,5.5,0.0,1.77,0.79,11.1,2.97,0.33,12.2,1.86,1.57,0.81,1.64,0.64,3.43,1.34,6.09,2.52,0.53200000000000002842,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(107,'60 x 40 x 5',3.79,4.83,60.0,40.0,5.0,6.0,0.0,1.97,0.98,17.5,6.28,0.41,20.2,3.65,1.91,1.14,2.04,0.87,4.35,2.08,7.83,3.77,0.39500000000000001776,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(108,'60 x 40 x 6',4.49,5.72,60.0,40.0,6.0,6.0,0.0,2.01,1.02,20.5,7.29,0.41,23.5,4.26,1.89,1.13,2.03,0.86,5.13,2.45,9.21,4.47,0.67600000000000015631,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(109,'60 x 40 x 8',5.84,7.44,60.0,40.0,8.0,6.0,0.0,2.08,1.09,25.9,9.12,0.4,29.6,5.45,1.87,1.11,1.99,0.86,6.62,3.14,11.8,5.83,1.57,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(110,'65 x 45 x 5',4.18,5.33,65.0,45.0,5.0,6.0,0.0,2.09,1.1,22.8,9.02,0.44,26.7,5.12,2.07,1.3,2.24,0.98,5.16,2.65,9.33,4.77,0.43700000000000001065,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(111,'65 x 45 x 6',4.96,6.32,65.0,45.0,6.0,6.0,0.0,2.13,1.14,26.7,10.5,0.44,31.2,5.99,2.06,1.29,2.22,0.97,6.1,3.12,11.0,5.66,0.74800000000000004263,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(112,'65 x 45 x 8',6.47,8.24,65.0,45.0,8.0,6.0,0.0,2.2,1.21,33.9,13.2,0.43,39.5,7.66,2.03,1.27,2.19,0.96,7.89,4.02,14.1,7.39,1.74,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(113,'70 x 45 x 5',4.39,5.59,70.0,45.0,5.0,6.5,0.0,2.29,1.06,28.0,9.2,0.39,31.8,5.42,2.24,1.28,2.39,0.98,5.95,2.68,10.7,4.82,0.4580000000000000071,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(114,'70 x 45 x 6',5.21,6.63,70.0,45.0,6.0,6.5,0.0,2.33,1.1,32.8,10.7,0.39,37.2,6.34,2.23,1.27,2.37,0.98,7.04,3.15,12.6,5.72,0.78399999999999998578,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(115,'70 x 45 x 8',6.79,8.65,70.0,45.0,8.0,6.5,0.0,2.41,1.18,41.8,13.5,0.38,47.2,8.1,2.2,1.25,2.34,0.97,9.12,4.06,16.3,7.5,1.82,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(116,'70 x 45 x 10',8.31,10.5,70.0,45.0,10.0,6.5,0.0,2.49,1.25,50.0,16.0,0.37,56.2,9.81,2.17,1.23,2.3,0.96,11.0,4.91,19.7,9.22,3.5,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(117,'75 x 50 x 5',4.78,6.09,75.0,50.0,5.0,6.5,0.0,2.41,1.18,35.1,12.7,0.41,40.5,7.32,2.4,1.44,2.58,1.1,6.9,3.32,12.4,5.95,0.5,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(118,'75 x 50 x 6',5.68,7.23,75.0,50.0,6.0,6.5,0.0,2.45,1.22,41.2,14.8,0.41,47.5,8.57,2.39,1.43,2.56,1.09,8.17,3.92,14.7,7.07,0.85600000000000004973,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(119,'75 x 50 x 8',7.42,9.45,75.0,50.0,8.0,6.5,0.0,2.53,1.29,52.7,18.7,0.41,60.5,10.9,2.36,1.41,2.53,1.08,10.6,5.05,19.0,9.25,1.99,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(120,'75 x 50 x 10',9.1,11.5,75.0,50.0,10.0,6.5,0.0,2.61,1.37,63.2,22.3,0.4,72.2,13.3,2.34,1.39,2.5,1.07,12.9,6.13,23.1,11.3,3.83,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(121,'80 x 50 x 5',4.99,6.36,80.0,50.0,5.0,7.0,0.0,2.62,1.14,42.0,12.9,0.37,47.3,7.68,2.57,1.43,2.73,1.1,7.81,3.34,14.0,5.99,0.52,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(122,'80 x 50 x 6',5.92,7.55,80.0,50.0,6.0,7.0,0.0,2.66,1.18,49.4,15.1,0.37,55.5,8.99,2.56,1.41,2.71,1.09,9.25,3.95,16.5,7.13,0.89199999999999999289,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(123,'80 x 50 x 8',7.74,9.87,80.0,50.0,8.0,7.0,0.0,2.74,1.26,63.2,19.1,0.37,70.8,11.5,2.53,1.39,2.68,1.08,12.0,5.09,21.4,9.36,2.08,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(124,'80 x 50 x 10',9.5,12.1,80.0,50.0,10.0,7.0,0.0,2.82,1.33,76.0,22.7,0.36,84.7,13.9,2.5,1.37,2.65,1.07,14.6,6.18,26.0,11.5,4.0,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(125,'90 x 60 x 6',6.88,8.76,90.0,60.0,6.0,7.5,0.0,2.9,1.42,72.8,26.3,0.41,84.0,15.2,2.88,1.73,3.1,1.32,11.9,5.74,21.5,10.2,1.03,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(126,'90 x 60 x 8',9.01,11.4,90.0,60.0,8.0,7.5,0.0,2.98,1.49,93.6,33.5,0.41,107.0,19.5,2.86,1.71,3.06,1.3,15.5,7.44,27.9,13.4,2.42,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(127,'90 x 60 x 10',11.08,14.1,90.0,60.0,10.0,7.5,0.0,3.06,1.57,113.0,40.1,0.41,129.0,23.6,2.83,1.69,3.03,1.29,19.0,9.05,34.1,16.6,4.66,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(128,'90 x 60 x 12',13.09,16.6,90.0,60.0,12.0,7.5,0.0,3.13,1.64,131.0,46.2,0.4,149.0,27.6,2.8,1.66,3.0,1.29,22.3,10.6,39.9,19.7,7.94,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(129,'100 x 65 x 6',7.6,9.68,100.0,65.0,6.0,8.0,0.0,3.22,1.5,100.0,34.0,0.4,114.0,19.8,3.22,1.88,3.44,1.43,14.8,6.8,26.6,12.1,1.14,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(130,'100 x 65 x 8',9.97,12.7,100.0,65.0,8.0,8.0,0.0,3.3,1.57,129.0,43.5,0.4,147.0,25.5,3.19,1.85,3.4,1.42,19.3,8.8,34.6,15.9,2.67,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(131,'100 x 65 x 10',12.28,15.6,100.0,65.0,10.0,8.0,0.0,3.38,1.65,156.0,52.2,0.39,177.0,30.9,3.16,1.83,3.37,1.4,23.6,10.8,42.3,19.7,5.16,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(132,'100 x 75 x 6',8.08,10.3,100.0,75.0,6.0,8.5,0.0,3.05,1.82,105.0,51.2,0.5,128.0,27.6,3.19,2.23,3.54,1.64,15.1,9.0,27.4,16.0,1.21,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(133,'100 x 75 x 8',10.61,13.5,100.0,75.0,8.0,8.5,0.0,3.13,1.89,135.0,65.7,0.5,165.0,35.5,3.17,2.21,3.5,1.62,19.7,11.7,35.8,21.0,2.85,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(134,'100 x 75 x 10',13.07,16.6,100.0,75.0,10.0,8.5,0.0,3.21,1.97,164.0,79.2,0.5,200.0,43.0,3.14,2.18,3.47,1.61,24.2,14.3,43.8,25.9,5.5,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(135,'100 x 75 x 12',15.48,19.7,100.0,75.0,12.0,8.5,0.0,3.28,2.04,191.0,91.7,0.5,232.0,50.4,3.11,2.16,3.43,1.6,28.5,16.8,51.4,30.6,9.38,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(136,'125 x 75 x 6',9.27,11.8,125.0,75.0,6.0,9.0,0.0,4.08,1.62,194.0,54.3,0.35,215.0,32.7,4.05,2.14,4.27,1.66,23.1,9.2,41.3,16.4,1.39,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(137,'125 x 75 x 8',12.19,15.5,125.0,75.0,8.0,9.0,0.0,4.17,1.7,251.0,69.7,0.35,279.0,42.1,4.03,2.12,4.24,1.65,30.2,12.0,53.9,21.6,3.27,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(138,'125 x 75 x 10',15.05,19.1,125.0,75.0,10.0,9.0,0.0,4.26,1.78,306.0,84.1,0.35,339.0,51.0,4.0,2.09,4.21,1.63,37.2,14.7,66.2,26.7,6.33,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(139,'125 x 95 x 6',10.14,12.9,125.0,95.0,6.0,9.0,4.8,3.72,2.24,205.0,103.0,0.52,254.0,55.0,3.99,2.83,4.44,2.06,23.4,14.3,42.9,25.5,1.54,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(140,'125 x 95 x 8',13.37,17.0,125.0,95.0,8.0,9.0,4.8,3.8,2.32,268.0,134.0,0.52,331.0,71.4,3.97,2.81,4.41,2.05,30.9,18.8,56.4,33.7,3.61,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(141,'125 x 95 x 10',16.54,21.0,125.0,95.0,10.0,9.0,4.8,3.89,2.4,328.0,164.0,0.51,404.0,87.3,3.94,2.79,4.38,2.04,38.1,23.1,69.4,41.7,7.0,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(142,'125 x 95 x 12',19.65,25.0,125.0,95.0,12.0,9.0,4.8,3.97,2.48,384.0,191.0,0.51,473.0,102.0,3.92,2.77,4.35,2.02,45.1,27.3,82.0,49.5,11.9,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(143,'150 x 115 x 8',16.27,20.7,150.0,115.0,8.0,11.0,4.8,4.48,2.76,474.0,244.0,0.52,589.0,128.0,4.78,3.43,5.34,2.49,45.1,27.9,82.4,50.0,4.38,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(144,'150 x 115 x 10',20.14,25.6,150.0,115.0,10.0,11.0,4.8,4.57,2.84,581.0,298.0,0.52,722.0,157.0,4.76,3.41,5.31,2.48,55.8,34.5,101.0,61.9,8.5,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(145,'150 x 115 x 12',23.96,30.5,150.0,115.0,12.0,11.0,4.8,4.65,2.92,684.0,350.0,0.52,849.0,185.0,4.74,3.39,5.28,2.47,66.2,40.8,120.0,73.5,14.5,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(146,'150 x 115 x 16',31.4,40.0,150.0,115.0,16.0,11.0,4.8,4.81,3.07,878.0,446.0,0.52,1080.0,239.0,4.69,3.34,5.21,2.44,86.2,53.0,156.0,96.1,33.9,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(147,'200 x 100 x 10',22.93,29.2,200.0,100.0,10.0,12.0,4.8,6.98,2.03,1220.0,214.0,0.26,1300.0,137.0,6.48,2.71,6.68,2.17,94.3,26.9,165.0,48.7,9.66,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(148,'200 x 100 x 12',27.29,34.7,200.0,100.0,12.0,12.0,4.8,7.07,2.11,1440.0,251.0,0.26,1530.0,161.0,6.46,2.69,6.65,2.15,112.0,31.9,196.0,58.3,16.5,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(149,'200 x 100 x 16',35.84,45.6,200.0,100.0,16.0,12.0,4.8,7.23,2.27,1870.0,319.0,0.25,1980.0,207.0,6.4,2.65,6.59,2.13,146.0,41.3,255.0,77.5,38.7,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(150,'200 x 150 x 10',26.92,34.2,200.0,150.0,10.0,13.5,4.8,6.02,3.55,1400.0,688.0,0.51,1720.0,368.0,6.41,4.48,7.1,3.28,100.0,60.2,183.0,107.0,11.3,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(151,'200 x 150 x 12',32.07,40.8,200.0,150.0,12.0,13.5,4.8,6.11,3.63,1660.0,812.0,0.51,2040.0,433.0,6.39,4.46,7.07,3.26,119.0,71.4,218.0,127.0,19.4,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(152,'200 x 150 x 16',42.18,53.7,200.0,150.0,16.0,13.5,4.8,6.27,3.79,2150.0,1040.0,0.5,2630.0,560.0,6.33,4.41,7.01,3.23,156.0,93.2,285.0,167.0,45.6,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(153,'200 x 150 x 20',52.04,66.2,200.0,150.0,20.0,13.5,4.8,6.42,3.94,2610.0,1260.0,0.5,3190.0,682.0,6.28,4.36,6.94,3.21,192.0,114.0,349.0,206.0,88.0,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(154,'40 x 20 x 3',1.37,1.74,40.0,20.0,3.0,4.0,0.0,1.43,0.45,2.9,0.49,0.25,3.04,0.32,1.28,0.53,1.32,0.43,1.11,0.32,1.95,0.59,0.050999999999999996447,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(155,'40 x 20 x 4',1.79,2.27,40.0,20.0,4.0,4.0,0.0,1.47,0.49,3.7,0.62,0.25,3.86,0.41,1.27,0.52,1.3,0.42,1.45,0.41,2.52,0.78,0.11899999999999999467,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(156,'40 x 20 x 5',2.19,2.78,40.0,20.0,5.0,4.0,0.0,1.51,0.52,4.4,0.73,0.24,4.62,0.49,1.25,0.51,1.29,0.42,1.76,0.49,3.05,0.97,0.22900000000000000355,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(157,'60 x 30 x 5',3.4,4.33,60.0,30.0,5.0,6.0,0.0,2.16,0.69,15.9,2.7,0.25,16.8,1.76,1.92,0.79,1.97,0.64,4.14,1.17,7.24,2.21,0.35400000000000000355,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(158,'60 x 30 x 6',4.02,5.12,60.0,30.0,6.0,6.0,0.0,2.21,0.73,18.5,3.11,0.25,19.6,2.06,1.9,0.78,1.96,0.63,4.88,1.37,8.5,2.64,0.60400000000000000355,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(159,'60 x 40 x 7',5.17,6.59,60.0,40.0,7.0,6.0,0.0,2.05,1.06,23.3,8.23,0.4,26.6,4.86,1.88,1.12,2.01,0.86,5.89,2.8,10.5,5.16,1.06,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(160,'65 x 50 x 5',4.38,5.58,65.0,50.0,5.0,6.0,0.0,2.0,1.26,23.6,12.2,0.52,29.3,6.48,2.06,1.48,2.29,1.08,5.25,3.27,9.53,5.85,0.4580000000000000071,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(161,'65 x 50 x 6',5.19,6.62,65.0,50.0,6.0,6.0,0.0,2.04,1.3,27.6,14.2,0.52,34.3,7.59,2.04,1.47,2.28,1.07,6.2,3.85,11.2,6.93,0.78399999999999998578,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(162,'65 x 50 x 7',6.0,7.64,65.0,50.0,7.0,6.0,0.0,2.08,1.34,31.5,16.2,0.52,39.0,8.67,2.03,1.45,2.26,1.07,7.13,4.42,12.9,7.99,1.23,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(163,'65 x 50 x 8',6.78,8.64,65.0,50.0,8.0,6.0,0.0,2.12,1.38,35.2,18.0,0.52,43.4,9.72,2.02,1.44,2.24,1.06,8.03,4.97,14.5,9.03,1.82,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(164,'70 x 50 x 5',4.57,5.83,70.0,50.0,5.0,6.0,0.0,2.21,1.22,29.0,12.5,0.46,34.5,6.92,2.23,1.46,2.43,1.09,6.05,3.3,10.9,5.9,0.47900000000000000355,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(165,'70 x 50 x 6',5.43,6.92,70.0,50.0,6.0,6.0,0.0,2.25,1.26,34.0,14.5,0.46,40.4,8.11,2.22,1.45,2.42,1.08,7.16,3.89,12.9,7.0,0.82,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(166,'70 x 50 x 7',6.27,7.99,70.0,50.0,7.0,6.0,0.0,2.29,1.3,38.8,16.5,0.46,46.0,9.26,2.2,1.44,2.4,1.08,8.23,4.46,14.8,8.08,1.29,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(167,'70 x 50 x 8',7.09,9.04,70.0,50.0,8.0,6.0,0.0,2.33,1.33,43.4,18.4,0.46,51.4,10.3,2.19,1.43,2.38,1.07,9.28,5.01,16.7,9.14,1.91,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(168,'75 x 50 x 7',6.57,8.37,75.0,50.0,7.0,7.0,0.0,2.49,1.26,47.1,16.8,0.41,54.2,9.8,2.37,1.42,2.54,1.08,9.41,4.49,16.9,8.17,1.34,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(169,'80 x 40 x 5',4.6,5.86,80.0,40.0,5.0,7.0,0.0,2.82,0.86,39.0,6.74,0.26,41.4,4.36,2.58,1.07,2.66,0.86,7.53,2.14,13.1,3.94,0.47900000000000000355,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(170,'80 x 40 x 6',5.45,6.95,80.0,40.0,6.0,7.0,0.0,2.86,0.89,45.7,7.84,0.25,48.5,5.09,2.57,1.06,2.64,0.86,8.9,2.52,15.5,4.7,0.82,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(171,'80 x 40 x 7',6.29,8.02,80.0,40.0,7.0,7.0,0.0,2.91,0.93,52.2,8.87,0.25,55.3,5.8,2.55,1.05,2.63,0.85,10.2,2.89,17.8,5.47,1.29,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(172,'80 x 40 x 8',7.12,9.07,80.0,40.0,8.0,7.0,0.0,2.95,0.97,58.4,9.84,0.25,61.7,6.5,2.54,1.04,2.61,0.85,11.5,3.25,20.1,6.24,1.91,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(173,'80 x 60 x 6',6.42,8.18,80.0,60.0,6.0,8.0,0.0,2.48,1.5,52.6,25.5,0.5,64.3,13.8,2.54,1.77,2.8,1.3,9.5,5.7,17.3,10.1,0.96400000000000005684,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(174,'80 x 60 x 7',7.42,9.45,80.0,60.0,7.0,8.0,0.0,2.52,1.54,60.1,29.1,0.5,73.4,15.8,2.52,1.75,2.79,1.29,11.0,6.5,19.9,11.7,1.52,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(175,'80 x 60 x 8',8.4,10.7,80.0,60.0,8.0,8.0,0.0,2.56,1.57,67.4,32.5,0.5,82.2,17.7,2.51,1.74,2.77,1.29,12.4,7.3,22.4,13.3,2.25,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(176,'90 x 65 x 6',7.13,9.08,90.0,65.0,6.0,8.0,0.0,2.81,1.57,74.8,33.1,0.47,89.7,18.3,2.87,1.91,3.14,1.42,12.1,6.7,21.9,12.0,1.07,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(177,'90 x 65 x 7',8.24,10.5,90.0,65.0,7.0,8.0,0.0,2.85,1.61,85.7,37.8,0.47,102.0,20.9,2.86,1.9,3.13,1.41,13.9,7.7,25.2,13.9,1.69,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(178,'90 x 65 x 8',9.34,11.9,90.0,65.0,8.0,8.0,0.0,2.89,1.65,96.3,42.3,0.47,115.0,23.5,2.84,1.89,3.11,1.4,15.8,8.7,28.5,15.7,2.5,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(179,'90 x 65 x 10',11.49,14.6,90.0,65.0,10.0,8.0,0.0,2.97,1.73,116.0,50.7,0.47,138.0,28.4,2.82,1.86,3.08,1.39,19.3,10.6,34.8,19.3,4.83,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(180,'100 x 50 x 6',6.92,8.81,100.0,50.0,6.0,9.0,0.0,3.51,1.06,91.9,15.9,0.26,97.5,10.3,3.23,1.34,3.33,1.08,14.2,4.0,24.8,7.4,1.03,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(181,'100 x 50 x 7',7.99,10.1,100.0,50.0,7.0,9.0,0.0,3.56,1.1,105.0,18.1,0.26,111.0,11.7,3.21,1.33,3.31,1.07,16.3,4.6,28.6,8.6,1.63,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(182,'100 x 50 x 8',9.05,11.5,100.0,50.0,8.0,9.0,0.0,3.6,1.14,118.0,20.2,0.25,125.0,13.1,3.2,1.32,3.29,1.07,18.5,5.2,32.2,9.8,2.42,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(183,'100 x 50 x 10',11.13,14.1,100.0,50.0,10.0,9.0,0.0,3.68,1.21,142.0,24.0,0.25,150.0,15.9,3.17,1.3,3.26,1.06,22.6,6.3,39.3,12.2,4.66,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(184,'100 x 65 x 7',8.85,11.2,100.0,65.0,7.0,10.0,0.0,3.25,1.53,115.0,38.9,0.4,131.0,22.8,3.2,1.86,3.41,1.42,17.1,7.8,30.7,14.1,1.8,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(185,'120 x 80 x 8',12.26,15.6,120.0,80.0,8.0,11.0,0.0,3.85,1.89,230.0,83.2,0.41,265.0,48.1,3.84,2.31,4.12,1.75,28.3,13.6,51.0,24.4,3.27,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(186,'120 x 80 x 10',15.12,19.2,120.0,80.0,10.0,11.0,0.0,3.94,1.96,280.0,100.0,0.41,322.0,58.3,3.81,2.28,4.09,1.74,34.8,16.6,62.6,30.1,6.33,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(187,'120 x 80 x 12',17.91,22.8,120.0,80.0,12.0,11.0,0.0,4.02,2.04,327.0,116.0,0.41,375.0,68.1,3.79,2.26,4.06,1.73,41.0,19.6,73.7,35.7,10.8,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(188,'125 x 75 x 12',17.91,22.8,125.0,75.0,12.0,11.0,0.0,4.32,1.85,358.0,97.5,0.34,396.0,59.8,3.97,2.07,4.17,1.62,43.9,17.3,78.1,31.8,10.8,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(189,'135 x 65 x 8',12.18,15.5,135.0,65.0,8.0,11.0,4.8,4.79,1.35,292.0,45.5,0.24,308.0,29.7,4.34,1.71,4.46,1.38,33.6,8.8,59.0,16.4,3.27,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(190,'135 x 65 x 10',15.04,19.1,135.0,65.0,10.0,11.0,4.8,4.88,1.43,357.0,55.0,0.24,376.0,36.1,4.32,1.69,4.43,1.37,41.4,10.8,72.5,20.5,6.33,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(191,'135 x 65 x 12',17.84,22.7,135.0,65.0,12.0,11.0,4.8,4.97,1.51,418.0,63.9,0.24,440.0,42.3,4.29,1.68,4.4,1.36,49.0,12.8,85.4,24.6,10.8,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(192,'150 x 75 x 9',15.39,19.6,150.0,75.0,9.0,11.0,4.8,5.28,1.58,457.0,78.8,0.26,485.0,50.7,4.83,2.01,4.98,1.61,47.1,13.3,82.8,24.5,5.24,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(193,'150 x 75 x 15',24.85,31.6,150.0,75.0,15.0,11.0,4.8,5.53,1.81,715.0,120.0,0.25,755.0,79.1,4.75,1.95,4.89,1.58,75.5,21.1,131.0,40.7,23.6,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(194,'150 x 90 x 10',18.22,23.2,150.0,90.0,10.0,12.0,4.8,5.0,2.04,536.0,147.0,0.35,594.0,89.1,4.81,2.52,5.06,1.96,53.6,21.2,96.2,38.4,7.66,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(195,'150 x 90 x 12',21.64,27.5,150.0,90.0,12.0,12.0,4.8,5.09,2.12,630.0,172.0,0.34,698.0,104.0,4.78,2.5,5.03,1.95,63.6,25.0,113.0,45.8,13.1,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(196,'150 x 90 x 15',26.66,33.9,150.0,90.0,15.0,12.0,4.8,5.21,2.24,764.0,206.0,0.34,843.0,126.0,4.74,2.47,4.98,1.93,78.0,30.6,139.0,56.7,25.3,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(197,'200 x 100 x 15',33.86,43.1,200.0,100.0,15.0,15.0,4.8,7.17,2.23,1770.0,303.0,0.25,1870.0,196.0,6.41,2.65,6.6,2.13,138.0,39.0,241.0,72.9,32.0,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(198,'200 x 150 x 15',39.75,50.6,200.0,150.0,15.0,15.0,4.8,6.22,3.75,2030.0,988.0,0.5,2490.0,530.0,6.34,4.42,7.02,3.24,147.0,87.8,268.0,157.0,37.6,'IS808_Rev',NULL); +INSERT INTO Angles VALUES(199,'200 x 150 x 18',47.21,60.1,200.0,150.0,18.0,15.0,4.8,6.34,3.86,2390.0,1150.0,0.5,2920.0,623.0,6.3,4.38,6.97,3.22,175.0,103.0,317.0,187.0,64.5,'IS808_Rev',NULL); +COMMIT; diff --git a/src/osdagbridge/core/data/ResourceFiles/Intg_osdag.sqlite b/src/osdagbridge/core/data/ResourceFiles/Intg_osdag.sqlite new file mode 100644 index 0000000000000000000000000000000000000000..b03b1a1672d9d8c16b2a42290708beb46e1f44e2 GIT binary patch literal 249856 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≪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 literal 0 HcmV?d00001 diff --git a/src/osdagbridge/core/data/ResourceFiles/precision.awk b/src/osdagbridge/core/data/ResourceFiles/precision.awk new file mode 100644 index 00000000..d44fd455 --- /dev/null +++ b/src/osdagbridge/core/data/ResourceFiles/precision.awk @@ -0,0 +1,81 @@ +#!/usr/bin/awk -f + +# awk script to reduce precision in output + +/^INSERT INTO / { + $0 = gensub(/(\.[0-9][1-9])00[0-9]*(,|\))/, "\\1\\2", "g"); + $0 = gensub(/(\.[0-9])000[0-9]*(,|\))/, "\\1\\2", "g"); + $0 = gensub(/(\.[0-9])099[0-9]*(,|\))/, "\\11\\2", "g"); + $0 = gensub(/(\.[0-9])199[0-9]*(,|\))/, "\\12\\2", "g"); + $0 = gensub(/(\.[0-9])299[0-9]*(,|\))/, "\\13\\2", "g"); + $0 = gensub(/(\.[0-9])399[0-9]*(,|\))/, "\\14\\2", "g"); + $0 = gensub(/(\.[0-9])499[0-9]*(,|\))/, "\\15\\2", "g"); + $0 = gensub(/(\.[0-9])599[0-9]*(,|\))/, "\\16\\2", "g"); + $0 = gensub(/(\.[0-9])699[0-9]*(,|\))/, "\\17\\2", "g"); + $0 = gensub(/(\.[0-9])799[0-9]*(,|\))/, "\\18\\2", "g"); + $0 = gensub(/(\.[0-9])899[0-9]*(,|\))/, "\\19\\2", "g"); + $0 = gensub(/(\.)0999[0-9]*(,|\))/, "\\11\\2", "g"); + $0 = gensub(/(\.)1999[0-9]*(,|\))/, "\\12\\2", "g"); + $0 = gensub(/(\.)2999[0-9]*(,|\))/, "\\13\\2", "g"); + $0 = gensub(/(\.)3999[0-9]*(,|\))/, "\\14\\2", "g"); + $0 = gensub(/(\.)4999[0-9]*(,|\))/, "\\15\\2", "g"); + $0 = gensub(/(\.)5999[0-9]*(,|\))/, "\\16\\2", "g"); + $0 = gensub(/(\.)6999[0-9]*(,|\))/, "\\17\\2", "g"); + $0 = gensub(/(\.)7999[0-9]*(,|\))/, "\\18\\2", "g"); + $0 = gensub(/(\.)8999[0-9]*(,|\))/, "\\19\\2", "g"); + $0 = gensub(/0(\.)9999[0-9]*(,|\))/, "1\\10\\2", "g"); + $0 = gensub(/1(\.)9999[0-9]*(,|\))/, "2\\10\\2", "g"); + $0 = gensub(/2(\.)9999[0-9]*(,|\))/, "3\\10\\2", "g"); + $0 = gensub(/3(\.)9999[0-9]*(,|\))/, "4\\10\\2", "g"); + $0 = gensub(/4(\.)9999[0-9]*(,|\))/, "5\\10\\2", "g"); + $0 = gensub(/5(\.)9999[0-9]*(,|\))/, "6\\10\\2", "g"); + $0 = gensub(/6(\.)9999[0-9]*(,|\))/, "7\\10\\2", "g"); + $0 = gensub(/7(\.)9999[0-9]*(,|\))/, "8\\10\\2", "g"); + $0 = gensub(/8(\.)9999[0-9]*(,|\))/, "9\\10\\2", "g"); + $0 = gensub(/\<9(\.)9999[0-9]*(,|\))/, "10\\10\\2", "g"); + $0 = gensub(/09(\.)9999[0-9]*(,|\))/, "10\\10\\2", "g"); + $0 = gensub(/19(\.)9999[0-9]*(,|\))/, "20\\10\\2", "g"); + $0 = gensub(/29(\.)9999[0-9]*(,|\))/, "30\\10\\2", "g"); + $0 = gensub(/39(\.)9999[0-9]*(,|\))/, "40\\10\\2", "g"); + $0 = gensub(/49(\.)9999[0-9]*(,|\))/, "50\\10\\2", "g"); + $0 = gensub(/59(\.)9999[0-9]*(,|\))/, "60\\10\\2", "g"); + $0 = gensub(/69(\.)9999[0-9]*(,|\))/, "70\\10\\2", "g"); + $0 = gensub(/79(\.)9999[0-9]*(,|\))/, "80\\10\\2", "g"); + $0 = gensub(/89(\.)9999[0-9]*(,|\))/, "90\\10\\2", "g"); + $0 = gensub(/\<99(\.)9999[0-9]*(,|\))/, "100\\10\\2", "g"); + $0 = gensub(/099(\.)9999[0-9]*(,|\))/, "100\\10\\2", "g"); + $0 = gensub(/199(\.)9999[0-9]*(,|\))/, "200\\10\\2", "g"); + $0 = gensub(/299(\.)9999[0-9]*(,|\))/, "300\\10\\2", "g"); + $0 = gensub(/399(\.)9999[0-9]*(,|\))/, "400\\10\\2", "g"); + $0 = gensub(/499(\.)9999[0-9]*(,|\))/, "500\\10\\2", "g"); + $0 = gensub(/599(\.)9999[0-9]*(,|\))/, "600\\10\\2", "g"); + $0 = gensub(/699(\.)9999[0-9]*(,|\))/, "700\\10\\2", "g"); + $0 = gensub(/799(\.)9999[0-9]*(,|\))/, "800\\10\\2", "g"); + $0 = gensub(/899(\.)9999[0-9]*(,|\))/, "900\\10\\2", "g"); + $0 = gensub(/\<999(\.)9999[0-9]*(,|\))/, "1000\\10\\2", "g"); + $0 = gensub(/0999(\.)9999[0-9]*(,|\))/, "1000\\10\\2", "g"); + $0 = gensub(/1999(\.)9999[0-9]*(,|\))/, "2000\\10\\2", "g"); + $0 = gensub(/2999(\.)9999[0-9]*(,|\))/, "3000\\10\\2", "g"); + $0 = gensub(/3999(\.)9999[0-9]*(,|\))/, "4000\\10\\2", "g"); + $0 = gensub(/4999(\.)9999[0-9]*(,|\))/, "5000\\10\\2", "g"); + $0 = gensub(/5999(\.)9999[0-9]*(,|\))/, "6000\\10\\2", "g"); + $0 = gensub(/6999(\.)9999[0-9]*(,|\))/, "7000\\10\\2", "g"); + $0 = gensub(/7999(\.)9999[0-9]*(,|\))/, "8000\\10\\2", "g"); + $0 = gensub(/8999(\.)9999[0-9]*(,|\))/, "9000\\10\\2", "g"); + $0 = gensub(/\<9999(\.)9999[0-9]*(,|\))/, "10000\\10\\2", "g"); + $0 = gensub(/09999(\.)9999[0-9]*(,|\))/, "10000\\10\\2", "g"); + $0 = gensub(/19999(\.)9999[0-9]*(,|\))/, "20000\\10\\2", "g"); + $0 = gensub(/29999(\.)9999[0-9]*(,|\))/, "30000\\10\\2", "g"); + $0 = gensub(/39999(\.)9999[0-9]*(,|\))/, "40000\\10\\2", "g"); + $0 = gensub(/49999(\.)9999[0-9]*(,|\))/, "50000\\10\\2", "g"); + $0 = gensub(/59999(\.)9999[0-9]*(,|\))/, "60000\\10\\2", "g"); + $0 = gensub(/69999(\.)9999[0-9]*(,|\))/, "70000\\10\\2", "g"); + $0 = gensub(/79999(\.)9999[0-9]*(,|\))/, "80000\\10\\2", "g"); + $0 = gensub(/89999(\.)9999[0-9]*(,|\))/, "90000\\10\\2", "g"); + print; + next; +} + +{ + print; +} \ No newline at end of file From 79bf24db3da3d104804718a20e0c4b8843f067e4 Mon Sep 17 00:00:00 2001 From: Sreejesh06 Date: Tue, 16 Dec 2025 15:37:39 +0530 Subject: [PATCH 50/59] Implement girder properties loader and UI preview for girder sections --- .../super_structure/girder/properties.py | 160 +++++++++++++++- .../desktop/ui/dialogs/additional_inputs.py | 179 ++++++++++++++++-- tests/unit/test_girder_properties.py | 29 +++ 3 files changed, 354 insertions(+), 14 deletions(-) create mode 100644 tests/unit/test_girder_properties.py diff --git a/src/osdagbridge/core/bridge_components/super_structure/girder/properties.py b/src/osdagbridge/core/bridge_components/super_structure/girder/properties.py index b363f1e5..d408f733 100644 --- a/src/osdagbridge/core/bridge_components/super_structure/girder/properties.py +++ b/src/osdagbridge/core/bridge_components/super_structure/girder/properties.py @@ -1,3 +1,157 @@ -"""Section property calculations (stub).""" -def area(): - return 0.0 +"""Access rolled girder properties sourced from the bundled SQLite table. + +The beam geometry and sectional properties are stored inside +``core/data/ResourceFiles/Intg_osdag.sqlite`` and originate from +IS 808:1989 (Rev.). This module provides a thin loader that exposes the +data both as rich ``BeamSection`` records and in the minimal format that +the UI preview widgets currently expect. +""" + +from __future__ import annotations + +import sqlite3 +from dataclasses import asdict, dataclass +from pathlib import Path +from typing import Any, Dict, Optional + + +@dataclass(frozen=True) +class BeamSection: + """Representation of a rolled steel beam from the reference table.""" + + id: int + designation: str + mass_per_meter_kg: float + area_cm2: float + depth_mm: float + flange_width_mm: float + web_thickness_mm: float + flange_thickness_mm: float + flange_slope: Optional[float] + root_radius_r1_mm: Optional[float] + root_radius_r2_mm: Optional[float] + moment_of_inertia_zz_cm4: Optional[float] + moment_of_inertia_yy_cm4: Optional[float] + radius_of_gyration_z_cm: Optional[float] + radius_of_gyration_y_cm: Optional[float] + elastic_section_modulus_z_cm3: Optional[float] + elastic_section_modulus_y_cm3: Optional[float] + plastic_section_modulus_z_cm3: Optional[float] + plastic_section_modulus_y_cm3: Optional[float] + torsion_constant_cm4: Optional[float] + warping_constant_cm6: Optional[float] + source: Optional[str] + section_type: Optional[str] + + @property + def outline_dict(self) -> Dict[str, float]: + """Return the minimal dictionary expected by preview widgets.""" + + return { + "designation": self.designation, + "depth_mm": self.depth_mm, + "top_flange_width_mm": self.flange_width_mm, + "bottom_flange_width_mm": self.flange_width_mm, + "web_thickness_mm": self.web_thickness_mm, + "top_flange_thickness_mm": self.flange_thickness_mm, + "bottom_flange_thickness_mm": self.flange_thickness_mm, + } + + def as_dict(self) -> Dict[str, Any]: + """Expose the full record as a plain dictionary copy.""" + + return asdict(self) + + +# Backwards compatibility for callers importing RolledSection directly. +RolledSection = BeamSection + +_DB_PATH = Path(__file__).resolve().parents[3] / "data" / "ResourceFiles" / "Intg_osdag.sqlite" +_BEAM_CACHE: Optional[Dict[str, BeamSection]] = None + + +def _text_or_none(value: Optional[str]) -> Optional[str]: + if value is None: + return None + text = value.strip() + return text or None + + +def _optional_float(value: Optional[float]) -> Optional[float]: + return None if value is None else float(value) + + +def _load_beam_sections() -> Dict[str, BeamSection]: + if not _DB_PATH.exists(): # pragma: no cover - defensive guard + raise FileNotFoundError(f"Beam property database not found at {_DB_PATH}") + + with sqlite3.connect(_DB_PATH) as connection: + connection.row_factory = sqlite3.Row + rows = connection.execute("SELECT * FROM Beams").fetchall() + + beam_map: Dict[str, BeamSection] = {} + for row in rows: + designation = row["Designation"] + beam = BeamSection( + id=int(row["Id"]), + designation=designation, + mass_per_meter_kg=float(row["Mass"]), + area_cm2=float(row["Area"]), + depth_mm=float(row["D"]), + flange_width_mm=float(row["B"]), + web_thickness_mm=float(row["tw"]), + flange_thickness_mm=float(row["T"]), + flange_slope=_optional_float(row["FlangeSlope"]), + root_radius_r1_mm=_optional_float(row["R1"]), + root_radius_r2_mm=_optional_float(row["R2"]), + moment_of_inertia_zz_cm4=_optional_float(row["Iz"]), + moment_of_inertia_yy_cm4=_optional_float(row["Iy"]), + radius_of_gyration_z_cm=_optional_float(row["rz"]), + radius_of_gyration_y_cm=_optional_float(row["ry"]), + elastic_section_modulus_z_cm3=_optional_float(row["Zz"]), + elastic_section_modulus_y_cm3=_optional_float(row["Zy"]), + plastic_section_modulus_z_cm3=_optional_float(row["Zpz"]), + plastic_section_modulus_y_cm3=_optional_float(row["Zpy"]), + torsion_constant_cm4=_optional_float(row["It"]), + warping_constant_cm6=_optional_float(row["Iw"]), + source=_text_or_none(row["Source"]), + section_type=_text_or_none(row["Type"]), + ) + beam_map[designation] = beam + + return beam_map + + +def _beams() -> Dict[str, BeamSection]: + global _BEAM_CACHE + if _BEAM_CACHE is None: + _BEAM_CACHE = _load_beam_sections() + return _BEAM_CACHE + + +def get_beam_profile(designation: str) -> Optional[BeamSection]: + """Return the full beam profile record from the SQLite table.""" + + return _beams().get(designation) + + +def get_rolled_section(designation: str) -> Optional[Dict[str, float]]: + """Return the rolled section outline expected by existing UI code.""" + + section = get_beam_profile(designation) + return section.outline_dict if section else None + + +def list_available_sections() -> Dict[str, BeamSection]: + """Expose the cached beam table for downstream consumers.""" + + return dict(_beams()) + + +__all__ = [ + "BeamSection", + "RolledSection", + "get_beam_profile", + "get_rolled_section", + "list_available_sections", +] diff --git a/src/osdagbridge/desktop/ui/dialogs/additional_inputs.py b/src/osdagbridge/desktop/ui/dialogs/additional_inputs.py index cfa265ca..3365fbff 100644 --- a/src/osdagbridge/desktop/ui/dialogs/additional_inputs.py +++ b/src/osdagbridge/desktop/ui/dialogs/additional_inputs.py @@ -11,9 +11,10 @@ QFrame, QGridLayout, QTableWidget, QTableWidgetItem, QHeaderView, QTextEdit, QDialog, QSizePolicy, QSizeGrip ) -from PySide6.QtCore import Qt, Signal, QSize -from PySide6.QtGui import QDoubleValidator, QIntValidator +from PySide6.QtCore import Qt, Signal, QSize, QRectF +from PySide6.QtGui import QDoubleValidator, QIntValidator, QPainter, QColor, QPen, QFont +from osdagbridge.core.bridge_components.super_structure.girder import properties as girder_properties from osdagbridge.core.utils.common import * from osdagbridge.desktop.ui.utils.custom_titlebar import CustomTitleBar @@ -1384,6 +1385,112 @@ def switch_section(self, index): for btn_index, button in enumerate(self.nav_buttons): button.setChecked(btn_index == index) + +class GirderSectionPreview(QWidget): + """Canvas that sketches an I-girder using the supplied dimensions.""" + + def __init__(self, parent=None): + super().__init__(parent) + self._section_data = None + self.setMinimumSize(240, 160) + self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) + + def update_section(self, section_data): + """Store the latest section data and trigger a repaint.""" + + self._section_data = section_data + self.update() + + def paintEvent(self, event): # noqa: N802 - Qt override + painter = QPainter(self) + painter.setRenderHint(QPainter.Antialiasing, True) + + canvas_rect = self.rect() + painter.fillRect(canvas_rect, QColor("#fafafa")) + painter.setPen(QPen(QColor("#d0d0d0"), 1)) + painter.drawRoundedRect(canvas_rect.adjusted(0, 0, -1, -1), 6, 6) + + if not self._section_data: + painter.setPen(QColor("#5a5a5a")) + font = painter.font() + font.setPointSizeF(max(9.0, font.pointSizeF())) + painter.setFont(font) + painter.drawText(canvas_rect.adjusted(12, 12, -12, -12), Qt.AlignCenter, + "Enter girder dimensions\nto preview the section") + return + + self._draw_section(painter, canvas_rect.adjusted(16, 16, -16, -16)) + + def _draw_section(self, painter, rect): + section = self._section_data or {} + depth = float(section.get("depth_mm") or 0) + top_width = float(section.get("top_flange_width_mm") or 0) + bottom_width = float(section.get("bottom_flange_width_mm") or top_width) + if not depth or not (top_width or bottom_width): + painter.setPen(QColor("#5a5a5a")) + painter.drawText(rect, Qt.AlignCenter, "Insufficient data") + return + + bottom_width = bottom_width or top_width + web_thickness = float(section.get("web_thickness_mm") or max(depth * 0.02, 10.0)) + top_thickness = float(section.get("top_flange_thickness_mm") or max(depth * 0.03, 12.0)) + bottom_thickness = float(section.get("bottom_flange_thickness_mm") or top_thickness) + + max_width = max(top_width, bottom_width, 1) + scale = min(rect.height() / depth, rect.width() / max_width) * 0.85 + center_x = rect.center().x() + top_y = rect.center().y() - (depth * scale) / 2.0 + + flange_color = QColor("#9cc35b") + web_color = QColor("#6e8f3d") + outline_pen = QPen(QColor("#4b5c2b"), 1) + + def _scaled_width(value): + return max(2.0, value * scale) + + top_flange_rect = QRectF( + center_x - _scaled_width(top_width) / 2.0, + top_y, + _scaled_width(top_width), + max(6.0, top_thickness * scale), + ) + + bottom_flange_rect = QRectF( + center_x - _scaled_width(bottom_width) / 2.0, + top_y + (depth - bottom_thickness) * scale, + _scaled_width(bottom_width), + max(6.0, bottom_thickness * scale), + ) + + web_rect = QRectF( + center_x - _scaled_width(web_thickness) / 2.0, + top_flange_rect.bottom(), + _scaled_width(web_thickness), + max(12.0, (depth - top_thickness - bottom_thickness) * scale), + ) + + painter.setPen(Qt.NoPen) + painter.setBrush(flange_color) + painter.drawRoundedRect(top_flange_rect, 2, 2) + painter.drawRoundedRect(bottom_flange_rect, 2, 2) + + painter.setBrush(web_color) + painter.drawRect(web_rect) + + painter.setPen(outline_pen) + painter.setBrush(Qt.NoBrush) + painter.drawRoundedRect(top_flange_rect, 2, 2) + painter.drawRoundedRect(bottom_flange_rect, 2, 2) + painter.drawRect(web_rect) + + # Annotate key dimensions + font = painter.font() + font.setPointSizeF(9) + painter.setFont(font) + painter.drawText(rect.left(), rect.bottom() - 4, f"Depth: {depth:.0f} mm") + painter.drawText(rect.left(), rect.bottom() - 20, f"Top flange: {top_width:.0f} mm") + painter.drawText(rect.left(), rect.bottom() - 36, f"Bottom flange: {bottom_width:.0f} mm") + class GirderDetailsTab(QWidget): """Tab for Girder Details styled to match the provided reference.""" @@ -1614,11 +1721,15 @@ def _build_section_card(self): image_layout.setContentsMargins(10, 10, 10, 10) image_layout.setSpacing(5) - self.dynamic_image_label = QLabel("Welded Girder") - self.dynamic_image_label.setAlignment(Qt.AlignCenter) - self.dynamic_image_label.setMinimumSize(240, 140) - self.dynamic_image_label.setStyleSheet("QLabel { border: 1px solid #d0d0d0; border-radius: 4px; background-color: #fafafa; font-weight: bold; color: #5b5b5b; }") - image_layout.addWidget(self.dynamic_image_label) + self.section_preview = GirderSectionPreview() + image_layout.addWidget(self.section_preview, 1) + + self.preview_caption = QLabel("Provide girder inputs to preview") + self.preview_caption.setAlignment(Qt.AlignCenter) + self.preview_caption.setStyleSheet( + "QLabel { font-size: 10px; color: #5b5b5b; border: none; padding-top: 4px; }" + ) + image_layout.addWidget(self.preview_caption) right_column_layout.addWidget(image_box) @@ -1668,6 +1779,9 @@ def _build_section_card(self): main_layout.addWidget(right_column) self.type_combo.currentTextChanged.connect(self._on_type_changed) + self.is_section_combo.currentTextChanged.connect(self._update_preview) + for watcher in (self.total_depth_input, self.top_width_input, self.bottom_width_input): + watcher.textChanged.connect(self._update_preview) self._on_type_changed(self.type_combo.currentText()) return container @@ -1713,10 +1827,7 @@ def _on_type_changed(self, text): is_welded = text.lower() == "welded" self._set_row_visibility(self.welded_rows, is_welded) self._set_row_visibility(self.rolled_rows, not is_welded) - if is_welded: - self.dynamic_image_label.setText("Welded Girder") - else: - self.dynamic_image_label.setText("Rolled Section") + self._update_preview() def _create_inner_box(self): """Create a bordered box for grouped controls""" @@ -1780,6 +1891,52 @@ def _set_row_visibility(self, rows, visible): label.setVisible(visible) widget.setVisible(visible) + def _update_preview(self): + if not hasattr(self, "section_preview"): + return + + is_welded = self.type_combo.currentText().lower() == "welded" + if is_welded: + section = self._gather_welded_dimensions() + caption = "Welded girder preview" if section else "Enter depth and flange widths" + else: + designation = self.is_section_combo.currentText() + section = girder_properties.get_rolled_section(designation) + caption = f"Rolled section • {designation}" if section else "Rolled section unavailable" + + self.section_preview.update_section(section) + if hasattr(self, "preview_caption"): + self.preview_caption.setText(caption) + + def _gather_welded_dimensions(self): + depth = self._parse_float(self.total_depth_input.text()) + top_width = self._parse_float(self.top_width_input.text()) + bottom_width = self._parse_float(self.bottom_width_input.text()) or top_width + + if not depth or not top_width or not bottom_width: + return None + + web_thickness = max(8.0, depth * 0.02) + flange_thickness = max(10.0, depth * 0.03) + + return { + "designation": "Custom Welded Girder", + "section_type": "welded", + "depth_mm": depth, + "top_flange_width_mm": top_width, + "bottom_flange_width_mm": bottom_width, + "web_thickness_mm": web_thickness, + "top_flange_thickness_mm": flange_thickness, + "bottom_flange_thickness_mm": flange_thickness, + } + + @staticmethod + def _parse_float(text): + try: + return float(text) + except (TypeError, ValueError): + return None + class StiffenerDetailsTab(QWidget): """Tab for Stiffener Details with compact layout""" diff --git a/tests/unit/test_girder_properties.py b/tests/unit/test_girder_properties.py new file mode 100644 index 00000000..5c03ec51 --- /dev/null +++ b/tests/unit/test_girder_properties.py @@ -0,0 +1,29 @@ +from osdagbridge.core.bridge_components.super_structure.girder import properties + + +def test_get_beam_profile_reads_from_resource_db(): + profile = properties.get_beam_profile("WB 500") + + assert profile is not None + assert profile.depth_mm == 500.0 + assert profile.flange_width_mm == 250.0 + assert profile.web_thickness_mm == 9.9 + + +def test_get_rolled_section_returns_outline_dict(): + outline = properties.get_rolled_section("WB 500") + + assert outline == { + "designation": "WB 500", + "depth_mm": 500.0, + "top_flange_width_mm": 250.0, + "bottom_flange_width_mm": 250.0, + "web_thickness_mm": 9.9, + "top_flange_thickness_mm": 14.7, + "bottom_flange_thickness_mm": 14.7, + } + + +def test_unknown_section_returns_none(): + assert properties.get_beam_profile("NON_EXISTENT") is None + assert properties.get_rolled_section("NON_EXISTENT") is None From 33a69a92b387d9573ea5198d1cbdeea333aceb5a Mon Sep 17 00:00:00 2001 From: Sreejesh06 Date: Thu, 18 Dec 2025 08:45:50 +0530 Subject: [PATCH 51/59] Refactor girder section preview and integrate rolled section preview widget --- .../desktop/ui/dialogs/additional_inputs.py | 162 +++----- .../ui/utils/rolled_section_preview.py | 349 ++++++++++++++++++ 2 files changed, 394 insertions(+), 117 deletions(-) create mode 100644 src/osdagbridge/desktop/ui/utils/rolled_section_preview.py diff --git a/src/osdagbridge/desktop/ui/dialogs/additional_inputs.py b/src/osdagbridge/desktop/ui/dialogs/additional_inputs.py index 3365fbff..3d2886fe 100644 --- a/src/osdagbridge/desktop/ui/dialogs/additional_inputs.py +++ b/src/osdagbridge/desktop/ui/dialogs/additional_inputs.py @@ -11,12 +11,13 @@ QFrame, QGridLayout, QTableWidget, QTableWidgetItem, QHeaderView, QTextEdit, QDialog, QSizePolicy, QSizeGrip ) -from PySide6.QtCore import Qt, Signal, QSize, QRectF -from PySide6.QtGui import QDoubleValidator, QIntValidator, QPainter, QColor, QPen, QFont +from PySide6.QtCore import Qt, Signal, QSize +from PySide6.QtGui import QDoubleValidator, QIntValidator from osdagbridge.core.bridge_components.super_structure.girder import properties as girder_properties from osdagbridge.core.utils.common import * from osdagbridge.desktop.ui.utils.custom_titlebar import CustomTitleBar +from osdagbridge.desktop.ui.utils.rolled_section_preview import RolledSectionPreview # ================================================================================= # CENTRALIZED STYLING @@ -1386,111 +1387,6 @@ def switch_section(self, index): button.setChecked(btn_index == index) -class GirderSectionPreview(QWidget): - """Canvas that sketches an I-girder using the supplied dimensions.""" - - def __init__(self, parent=None): - super().__init__(parent) - self._section_data = None - self.setMinimumSize(240, 160) - self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) - - def update_section(self, section_data): - """Store the latest section data and trigger a repaint.""" - - self._section_data = section_data - self.update() - - def paintEvent(self, event): # noqa: N802 - Qt override - painter = QPainter(self) - painter.setRenderHint(QPainter.Antialiasing, True) - - canvas_rect = self.rect() - painter.fillRect(canvas_rect, QColor("#fafafa")) - painter.setPen(QPen(QColor("#d0d0d0"), 1)) - painter.drawRoundedRect(canvas_rect.adjusted(0, 0, -1, -1), 6, 6) - - if not self._section_data: - painter.setPen(QColor("#5a5a5a")) - font = painter.font() - font.setPointSizeF(max(9.0, font.pointSizeF())) - painter.setFont(font) - painter.drawText(canvas_rect.adjusted(12, 12, -12, -12), Qt.AlignCenter, - "Enter girder dimensions\nto preview the section") - return - - self._draw_section(painter, canvas_rect.adjusted(16, 16, -16, -16)) - - def _draw_section(self, painter, rect): - section = self._section_data or {} - depth = float(section.get("depth_mm") or 0) - top_width = float(section.get("top_flange_width_mm") or 0) - bottom_width = float(section.get("bottom_flange_width_mm") or top_width) - if not depth or not (top_width or bottom_width): - painter.setPen(QColor("#5a5a5a")) - painter.drawText(rect, Qt.AlignCenter, "Insufficient data") - return - - bottom_width = bottom_width or top_width - web_thickness = float(section.get("web_thickness_mm") or max(depth * 0.02, 10.0)) - top_thickness = float(section.get("top_flange_thickness_mm") or max(depth * 0.03, 12.0)) - bottom_thickness = float(section.get("bottom_flange_thickness_mm") or top_thickness) - - max_width = max(top_width, bottom_width, 1) - scale = min(rect.height() / depth, rect.width() / max_width) * 0.85 - center_x = rect.center().x() - top_y = rect.center().y() - (depth * scale) / 2.0 - - flange_color = QColor("#9cc35b") - web_color = QColor("#6e8f3d") - outline_pen = QPen(QColor("#4b5c2b"), 1) - - def _scaled_width(value): - return max(2.0, value * scale) - - top_flange_rect = QRectF( - center_x - _scaled_width(top_width) / 2.0, - top_y, - _scaled_width(top_width), - max(6.0, top_thickness * scale), - ) - - bottom_flange_rect = QRectF( - center_x - _scaled_width(bottom_width) / 2.0, - top_y + (depth - bottom_thickness) * scale, - _scaled_width(bottom_width), - max(6.0, bottom_thickness * scale), - ) - - web_rect = QRectF( - center_x - _scaled_width(web_thickness) / 2.0, - top_flange_rect.bottom(), - _scaled_width(web_thickness), - max(12.0, (depth - top_thickness - bottom_thickness) * scale), - ) - - painter.setPen(Qt.NoPen) - painter.setBrush(flange_color) - painter.drawRoundedRect(top_flange_rect, 2, 2) - painter.drawRoundedRect(bottom_flange_rect, 2, 2) - - painter.setBrush(web_color) - painter.drawRect(web_rect) - - painter.setPen(outline_pen) - painter.setBrush(Qt.NoBrush) - painter.drawRoundedRect(top_flange_rect, 2, 2) - painter.drawRoundedRect(bottom_flange_rect, 2, 2) - painter.drawRect(web_rect) - - # Annotate key dimensions - font = painter.font() - font.setPointSizeF(9) - painter.setFont(font) - painter.drawText(rect.left(), rect.bottom() - 4, f"Depth: {depth:.0f} mm") - painter.drawText(rect.left(), rect.bottom() - 20, f"Top flange: {top_width:.0f} mm") - painter.drawText(rect.left(), rect.bottom() - 36, f"Bottom flange: {bottom_width:.0f} mm") - class GirderDetailsTab(QWidget): """Tab for Girder Details styled to match the provided reference.""" @@ -1661,10 +1557,7 @@ def _build_section_card(self): row = self._add_box_row(inputs_grid, row, "Bottom Flange Thickness (mm):", self.bottom_thickness_combo, self.welded_rows) self.is_section_combo = QComboBox() - self.is_section_combo.addItems([ - "ISMB 500", "ISMB 550", "ISMB 600", - "ISWB 500", "ISWB 550", "ISWB 600" - ]) + self._populate_rolled_section_combo() apply_field_style(self.is_section_combo) self._add_box_row(inputs_grid, row, "IS Section:", self.is_section_combo, self.rolled_rows) @@ -1721,7 +1614,7 @@ def _build_section_card(self): image_layout.setContentsMargins(10, 10, 10, 10) image_layout.setSpacing(5) - self.section_preview = GirderSectionPreview() + self.section_preview = RolledSectionPreview() image_layout.addWidget(self.section_preview, 1) self.preview_caption = QLabel("Provide girder inputs to preview") @@ -1891,20 +1784,55 @@ def _set_row_visibility(self, rows, visible): label.setVisible(visible) widget.setVisible(visible) + def _populate_rolled_section_combo(self): + designations = sorted(girder_properties.list_available_sections().keys()) + if not designations: + designations = [ + "ISMB 500", "ISMB 550", "ISMB 600", + "ISWB 500", "ISWB 550", "ISWB 600", + ] + self.is_section_combo.clear() + self.is_section_combo.addItems(designations) + def _update_preview(self): if not hasattr(self, "section_preview"): return is_welded = self.type_combo.currentText().lower() == "welded" if is_welded: - section = self._gather_welded_dimensions() - caption = "Welded girder preview" if section else "Enter depth and flange widths" + dims = self._gather_welded_dimensions() + caption = "Welded girder preview" if dims else "Enter depth and flange widths" + if dims: + self.section_preview.set_dimensions( + depth_mm=dims["depth_mm"], + flange_width_mm=dims["top_flange_width_mm"], + bottom_flange_width_mm=dims["bottom_flange_width_mm"], + web_thickness_mm=dims["web_thickness_mm"], + flange_thickness_mm=dims["top_flange_thickness_mm"], + bottom_flange_thickness_mm=dims["bottom_flange_thickness_mm"], + ) + else: + self.section_preview.clear() else: designation = self.is_section_combo.currentText() - section = girder_properties.get_rolled_section(designation) - caption = f"Rolled section • {designation}" if section else "Rolled section unavailable" + beam = girder_properties.get_beam_profile(designation) + outline = girder_properties.get_rolled_section(designation) if beam is None else None + has_data = bool(beam or outline) + caption = f"Rolled section • {designation}" if has_data else "Rolled section unavailable" + if beam: + self.section_preview.set_section(beam) + elif outline: + self.section_preview.set_dimensions( + depth_mm=outline["depth_mm"], + flange_width_mm=outline["top_flange_width_mm"], + bottom_flange_width_mm=outline["bottom_flange_width_mm"], + web_thickness_mm=outline["web_thickness_mm"], + flange_thickness_mm=outline["top_flange_thickness_mm"], + bottom_flange_thickness_mm=outline["bottom_flange_thickness_mm"], + ) + else: + self.section_preview.clear() - self.section_preview.update_section(section) if hasattr(self, "preview_caption"): self.preview_caption.setText(caption) diff --git a/src/osdagbridge/desktop/ui/utils/rolled_section_preview.py b/src/osdagbridge/desktop/ui/utils/rolled_section_preview.py new file mode 100644 index 00000000..18d343d4 --- /dev/null +++ b/src/osdagbridge/desktop/ui/utils/rolled_section_preview.py @@ -0,0 +1,349 @@ +"""Interactive beam preview widget with CAD-style annotations.""" + +from __future__ import annotations + +import math +from typing import Dict, Optional + +from PySide6.QtCore import QPointF, QRectF, Qt +from PySide6.QtGui import QColor, QFont, QFontMetricsF, QPainter, QPaintEvent, QPen +from PySide6.QtWidgets import QSizePolicy, QWidget + +from osdagbridge.core.bridge_components.super_structure.girder.properties import BeamSection + + +class RolledSectionPreview(QWidget): + """Render a rolled section with dimension annotations.""" + + def __init__(self, parent: Optional[QWidget] = None) -> None: + super().__init__(parent) + self._section: Optional[BeamSection] = None + self._dimensions: Dict[str, float] = {} + + self._outline_color = QColor("#1b1b1b") + self._outline_width = 3.0 + self._dimension_color = QColor("#1e88ff") + self._label_bg = QColor(255, 255, 255, 230) + self._text_color = QColor("#0f0f0f") + + self._outer_margin = 16 + self._annotation_margin_top = 36 + self._annotation_margin_bottom = 26 + self._annotation_margin_left = 52 + self._annotation_margin_right = 74 + self._dim_gap = 12 + self._arrow_size = 9 + + self.setMinimumSize(360, 260) + self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) + + # ------------------------------------------------------------------ + # Public API + # ------------------------------------------------------------------ + def set_section(self, section: Optional[BeamSection]) -> None: + """Update the preview to show the supplied ``BeamSection``.""" + + self._section = section + if section is None: + self._dimensions = {} + else: + self._dimensions = { + "depth": float(section.depth_mm), + "top_flange_width": float(section.flange_width_mm), + "bottom_flange_width": float(section.flange_width_mm), + "web_thickness": float(section.web_thickness_mm), + "top_flange_thickness": float(section.flange_thickness_mm), + "bottom_flange_thickness": float(section.flange_thickness_mm), + } + self.update() + + def set_dimensions( + self, + *, + depth_mm: float, + flange_width_mm: float, + web_thickness_mm: float, + flange_thickness_mm: float, + bottom_flange_width_mm: Optional[float] = None, + bottom_flange_thickness_mm: Optional[float] = None, + ) -> None: + """Feed custom dimensions (e.g., welded sections) directly.""" + + self._section = None + self._dimensions = { + "depth": float(depth_mm), + "top_flange_width": float(flange_width_mm), + "bottom_flange_width": float(bottom_flange_width_mm or flange_width_mm), + "web_thickness": float(web_thickness_mm), + "top_flange_thickness": float(flange_thickness_mm), + "bottom_flange_thickness": float(bottom_flange_thickness_mm or flange_thickness_mm), + } + self.update() + + def clear(self) -> None: + """Reset the preview to an empty placeholder.""" + + self._section = None + self._dimensions = {} + self.update() + + # ------------------------------------------------------------------ + # QWidget overrides + # ------------------------------------------------------------------ + def paintEvent(self, event: QPaintEvent) -> None: # noqa: D401 - Qt override + painter = QPainter(self) + painter.setRenderHint(QPainter.Antialiasing, True) + painter.fillRect(self.rect(), self.palette().window()) + + if not self._dimensions: + self._draw_placeholder(painter) + return + + dims = self._dimensions + depth = max(dims.get("depth", 0.0), 1.0) + top_width = max(dims.get("top_flange_width", 0.0), 1.0) + bottom_width = max(dims.get("bottom_flange_width", top_width), 1.0) + web_thickness = max(dims.get("web_thickness", 0.0), 0.5) + top_thickness = max(dims.get("top_flange_thickness", 0.0), 0.5) + bottom_thickness = max(dims.get("bottom_flange_thickness", top_thickness), 0.5) + + usable_rect = self.rect().adjusted( + self._outer_margin, + self._outer_margin, + -self._outer_margin, + -self._outer_margin, + ) + beam_rect = QRectF( + usable_rect.left() + self._annotation_margin_left, + usable_rect.top() + self._annotation_margin_top, + max(20.0, usable_rect.width() - self._annotation_margin_left - self._annotation_margin_right), + max(20.0, usable_rect.height() - self._annotation_margin_top - self._annotation_margin_bottom), + ) + + max_width = max(top_width, bottom_width) + scale_w = beam_rect.width() / max_width + scale_h = beam_rect.height() / depth + scale = min(scale_w, scale_h) * 0.92 + + beam_height = depth * scale + top_height = min(top_thickness * scale, beam_height * 0.4) + bottom_height = min(bottom_thickness * scale, beam_height * 0.4) + web_height = max(beam_height - top_height - bottom_height, scale * 2.0) + + center_x = beam_rect.center().x() + top_y = beam_rect.center().y() - (top_height + web_height + bottom_height) / 2.0 + + top_flange = QRectF( + center_x - (top_width * scale) / 2.0, + top_y, + top_width * scale, + top_height, + ) + web = QRectF( + center_x - (web_thickness * scale) / 2.0, + top_flange.bottom(), + max(1.0, web_thickness * scale), + web_height, + ) + bottom_flange = QRectF( + center_x - (bottom_width * scale) / 2.0, + web.bottom(), + bottom_width * scale, + bottom_height, + ) + + painter.save() + outline_pen = QPen(self._outline_color, self._outline_width) + outline_pen.setJoinStyle(Qt.MiterJoin) + painter.setPen(outline_pen) + painter.setBrush(QColor("#fefefe")) + painter.drawRect(top_flange) + painter.drawRect(web) + painter.drawRect(bottom_flange) + painter.restore() + + dim_pen = QPen(self._dimension_color, 1.6) + dim_pen.setCosmetic(True) + dim_pen.setCapStyle(Qt.FlatCap) + painter.setPen(dim_pen) + + font = QFont(self.font()) + font.setPointSizeF(max(9.0, font.pointSizeF())) + painter.setFont(font) + painter.setPen(dim_pen) + + # --- Top flange width dimension --- + width_dim_y = top_flange.top() - self._dim_gap + painter.drawLine(QPointF(top_flange.left(), top_flange.top()), QPointF(top_flange.left(), width_dim_y)) + painter.drawLine(QPointF(top_flange.right(), top_flange.top()), QPointF(top_flange.right(), width_dim_y)) + self._draw_dimension_line( + painter, + QPointF(top_flange.left(), width_dim_y), + QPointF(top_flange.right(), width_dim_y), + ) + self._draw_label( + painter, + f"Top flange width: {self._format_mm(top_width)}", + QPointF(top_flange.center().x(), width_dim_y - 10), + Qt.AlignHCenter | Qt.AlignBottom, + ) + + # --- Flange thickness dimension (top) --- + flange_dim_x = top_flange.left() - self._dim_gap + painter.drawLine(QPointF(top_flange.left(), top_flange.top()), QPointF(flange_dim_x, top_flange.top())) + painter.drawLine(QPointF(top_flange.left(), top_flange.bottom()), QPointF(flange_dim_x, top_flange.bottom())) + self._draw_dimension_line( + painter, + QPointF(flange_dim_x, top_flange.top()), + QPointF(flange_dim_x, top_flange.bottom()), + ) + self._draw_label( + painter, + f"Top flange thickness: {self._format_mm(top_thickness)}", + QPointF(flange_dim_x - 8, top_flange.center().y()), + Qt.AlignRight | Qt.AlignVCenter, + ) + + # --- Bottom flange width dimension --- + bottom_width_dim_y = bottom_flange.bottom() + self._dim_gap + painter.drawLine(QPointF(bottom_flange.left(), bottom_flange.bottom()), QPointF(bottom_flange.left(), bottom_width_dim_y)) + painter.drawLine(QPointF(bottom_flange.right(), bottom_flange.bottom()), QPointF(bottom_flange.right(), bottom_width_dim_y)) + self._draw_dimension_line( + painter, + QPointF(bottom_flange.left(), bottom_width_dim_y), + QPointF(bottom_flange.right(), bottom_width_dim_y), + ) + self._draw_label( + painter, + f"Bottom flange width: {self._format_mm(bottom_width)}", + QPointF(bottom_flange.center().x(), bottom_width_dim_y + 10), + Qt.AlignHCenter | Qt.AlignTop, + ) + + # --- Flange thickness dimension (bottom) --- + bottom_thickness_dim_x = bottom_flange.right() + self._dim_gap + self._arrow_size * 1.5 + painter.drawLine(QPointF(bottom_flange.right(), bottom_flange.top()), QPointF(bottom_thickness_dim_x, bottom_flange.top())) + painter.drawLine(QPointF(bottom_flange.right(), bottom_flange.bottom()), QPointF(bottom_thickness_dim_x, bottom_flange.bottom())) + self._draw_dimension_line( + painter, + QPointF(bottom_thickness_dim_x, bottom_flange.top()), + QPointF(bottom_thickness_dim_x, bottom_flange.bottom()), + ) + self._draw_label( + painter, + f"Bottom flange thickness: {self._format_mm(bottom_thickness)}", + QPointF(bottom_thickness_dim_x + 8, bottom_flange.center().y()), + Qt.AlignLeft | Qt.AlignVCenter, + ) + + # --- Overall depth dimension --- + depth_dim_x = bottom_thickness_dim_x + self._dim_gap + self._arrow_size * 2.0 + painter.drawLine(QPointF(bottom_flange.right(), top_flange.top()), QPointF(depth_dim_x, top_flange.top())) + painter.drawLine(QPointF(bottom_flange.right(), bottom_flange.bottom()), QPointF(depth_dim_x, bottom_flange.bottom())) + self._draw_dimension_line( + painter, + QPointF(depth_dim_x, top_flange.top()), + QPointF(depth_dim_x, bottom_flange.bottom()), + ) + self._draw_label( + painter, + f"Depth: {self._format_mm(depth)}", + QPointF(depth_dim_x + 10, (top_flange.top() + bottom_flange.bottom()) / 2.0), + Qt.AlignLeft | Qt.AlignVCenter, + ) + + # --- Web thickness dimension --- + mid_y = web.center().y() + self._draw_dimension_line( + painter, + QPointF(web.left(), mid_y), + QPointF(web.right(), mid_y), + ) + painter.drawLine(QPointF(web.left(), mid_y), QPointF(web.left(), mid_y + self._dim_gap * 0.7)) + painter.drawLine(QPointF(web.right(), mid_y), QPointF(web.right(), mid_y + self._dim_gap * 0.7)) + self._draw_label( + painter, + f"Web thickness: {self._format_mm(web_thickness)}", + QPointF(web.center().x(), mid_y + self._dim_gap * 0.7 + 6), + Qt.AlignHCenter | Qt.AlignTop, + ) + + # ------------------------------------------------------------------ + # Drawing helpers + # ------------------------------------------------------------------ + def _draw_placeholder(self, painter: QPainter) -> None: + painter.save() + pen = QPen(QColor("#b7b7b7"), 1.2, Qt.DashLine) + pen.setCosmetic(True) + painter.setPen(pen) + painter.drawRect(self.rect().adjusted(12, 12, -12, -12)) + painter.setPen(QColor("#6f6f6f")) + font = QFont(self.font()) + font.setPointSizeF(max(font.pointSizeF(), 10.0)) + painter.setFont(font) + painter.drawText(self.rect(), Qt.AlignCenter, "Select a rolled section to preview") + painter.restore() + + def _draw_dimension_line(self, painter: QPainter, start: QPointF, end: QPointF) -> None: + direction = QPointF(end.x() - start.x(), end.y() - start.y()) + length = math.hypot(direction.x(), direction.y()) + if length == 0: + return + unit = QPointF(direction.x() / length, direction.y() / length) + offset = unit * (self._arrow_size * 0.7) + painter.drawLine(start + offset, end - offset) + self._draw_arrow_head(painter, start, direction) + self._draw_arrow_head(painter, end, QPointF(-direction.x(), -direction.y())) + + def _draw_arrow_head(self, painter: QPainter, tip: QPointF, direction: QPointF) -> None: + length = math.hypot(direction.x(), direction.y()) + if length == 0: + return + unit = QPointF(direction.x() / length, direction.y() / length) + normal = QPointF(-unit.y(), unit.x()) + arrow = self._arrow_size + base = tip + unit * (arrow * 0.9) + left = base + normal * (arrow * 0.45) + right = base - normal * (arrow * 0.45) + painter.save() + painter.setBrush(self._dimension_color) + painter.setPen(Qt.NoPen) + painter.drawPolygon([tip, left, right]) + painter.restore() + + def _draw_label(self, painter: QPainter, text: str, anchor: QPointF, align: Qt.Alignment) -> None: + metrics = QFontMetricsF(painter.font()) + text_rect = metrics.boundingRect(text).adjusted(-6, -3, 6, 3) + rect = QRectF(0, 0, text_rect.width(), text_rect.height()) + + if align & Qt.AlignLeft: + rect.moveLeft(anchor.x()) + elif align & Qt.AlignRight: + rect.moveRight(anchor.x()) + else: + rect.moveCenter(QPointF(anchor.x(), rect.center().y())) + + if align & Qt.AlignTop: + rect.moveTop(anchor.y()) + elif align & Qt.AlignBottom: + rect.moveBottom(anchor.y()) + else: + rect.moveCenter(QPointF(rect.center().x(), anchor.y())) + + painter.save() + painter.setPen(Qt.NoPen) + painter.setBrush(self._label_bg) + painter.drawRoundedRect(rect, 4, 4) + painter.restore() + + painter.save() + painter.setPen(self._text_color) + painter.drawText(rect, Qt.AlignCenter, text) + painter.restore() + + @staticmethod + def _format_mm(value: float) -> str: + rounded = round(value) + if abs(value - rounded) < 0.01: + return f"{rounded} mm" + return f"{value:.1f} mm" From b3fb1488f7d6d874b3b1581d50087cfd69d87710 Mon Sep 17 00:00:00 2001 From: Sreejesh06 Date: Thu, 18 Dec 2025 22:37:00 +0530 Subject: [PATCH 52/59] Enhance RolledSectionPreview with CAD-style dimension annotations and color-coded dimensions --- .../ui/utils/rolled_section_preview.py | 134 ++++++++++++------ 1 file changed, 88 insertions(+), 46 deletions(-) diff --git a/src/osdagbridge/desktop/ui/utils/rolled_section_preview.py b/src/osdagbridge/desktop/ui/utils/rolled_section_preview.py index 18d343d4..34253225 100644 --- a/src/osdagbridge/desktop/ui/utils/rolled_section_preview.py +++ b/src/osdagbridge/desktop/ui/utils/rolled_section_preview.py @@ -13,7 +13,7 @@ class RolledSectionPreview(QWidget): - """Render a rolled section with dimension annotations.""" + """Render a rolled section with CAD-style dimension annotations.""" def __init__(self, parent: Optional[QWidget] = None) -> None: super().__init__(parent) @@ -23,6 +23,14 @@ def __init__(self, parent: Optional[QWidget] = None) -> None: self._outline_color = QColor("#1b1b1b") self._outline_width = 3.0 self._dimension_color = QColor("#1e88ff") + self._dimension_palette = { + "tfw": QColor("#1e88ff"), + "tft": QColor("#00a152"), + "bfw": QColor("#ff8f00"), + "bft": QColor("#f4511e"), + "d": QColor("#5e35b1"), + "wt": QColor("#00838f"), + } self._label_bg = QColor(255, 255, 255, 230) self._text_color = QColor("#0f0f0f") @@ -108,7 +116,7 @@ def paintEvent(self, event: QPaintEvent) -> None: # noqa: D401 - Qt override bottom_thickness = max(dims.get("bottom_flange_thickness", top_thickness), 0.5) usable_rect = self.rect().adjusted( - self._outer_margin, + self._outer_margin + 20, # extra left space for labels self._outer_margin, -self._outer_margin, -self._outer_margin, @@ -162,17 +170,12 @@ def paintEvent(self, event: QPaintEvent) -> None: # noqa: D401 - Qt override painter.drawRect(bottom_flange) painter.restore() - dim_pen = QPen(self._dimension_color, 1.6) - dim_pen.setCosmetic(True) - dim_pen.setCapStyle(Qt.FlatCap) - painter.setPen(dim_pen) - font = QFont(self.font()) font.setPointSizeF(max(9.0, font.pointSizeF())) painter.setFont(font) - painter.setPen(dim_pen) # --- Top flange width dimension --- + tfw_color = self._set_dimension_pen(painter, "tfw") width_dim_y = top_flange.top() - self._dim_gap painter.drawLine(QPointF(top_flange.left(), top_flange.top()), QPointF(top_flange.left(), width_dim_y)) painter.drawLine(QPointF(top_flange.right(), top_flange.top()), QPointF(top_flange.right(), width_dim_y)) @@ -180,15 +183,19 @@ def paintEvent(self, event: QPaintEvent) -> None: # noqa: D401 - Qt override painter, QPointF(top_flange.left(), width_dim_y), QPointF(top_flange.right(), width_dim_y), + tfw_color, ) self._draw_label( painter, - f"Top flange width: {self._format_mm(top_width)}", - QPointF(top_flange.center().x(), width_dim_y - 10), + f"TFW: {self._format_mm(top_width)}", + QPointF(top_flange.center().x(), width_dim_y - 6), Qt.AlignHCenter | Qt.AlignBottom, + with_background=False, + color=tfw_color, ) - # --- Flange thickness dimension (top) --- + # --- Top flange thickness dimension --- + tft_color = self._set_dimension_pen(painter, "tft") flange_dim_x = top_flange.left() - self._dim_gap painter.drawLine(QPointF(top_flange.left(), top_flange.top()), QPointF(flange_dim_x, top_flange.top())) painter.drawLine(QPointF(top_flange.left(), top_flange.bottom()), QPointF(flange_dim_x, top_flange.bottom())) @@ -196,15 +203,19 @@ def paintEvent(self, event: QPaintEvent) -> None: # noqa: D401 - Qt override painter, QPointF(flange_dim_x, top_flange.top()), QPointF(flange_dim_x, top_flange.bottom()), + tft_color, ) self._draw_label( painter, - f"Top flange thickness: {self._format_mm(top_thickness)}", - QPointF(flange_dim_x - 8, top_flange.center().y()), + f"TFT: {self._format_mm(top_thickness)}", + QPointF(flange_dim_x - 4, top_flange.center().y()), Qt.AlignRight | Qt.AlignVCenter, + with_background=False, + color=tft_color, ) # --- Bottom flange width dimension --- + bfw_color = self._set_dimension_pen(painter, "bfw") bottom_width_dim_y = bottom_flange.bottom() + self._dim_gap painter.drawLine(QPointF(bottom_flange.left(), bottom_flange.bottom()), QPointF(bottom_flange.left(), bottom_width_dim_y)) painter.drawLine(QPointF(bottom_flange.right(), bottom_flange.bottom()), QPointF(bottom_flange.right(), bottom_width_dim_y)) @@ -212,60 +223,72 @@ def paintEvent(self, event: QPaintEvent) -> None: # noqa: D401 - Qt override painter, QPointF(bottom_flange.left(), bottom_width_dim_y), QPointF(bottom_flange.right(), bottom_width_dim_y), + bfw_color, ) self._draw_label( painter, - f"Bottom flange width: {self._format_mm(bottom_width)}", - QPointF(bottom_flange.center().x(), bottom_width_dim_y + 10), + f"BFW: {self._format_mm(bottom_width)}", + QPointF(bottom_flange.center().x(), bottom_width_dim_y + 6), Qt.AlignHCenter | Qt.AlignTop, + with_background=False, + color=bfw_color, ) - # --- Flange thickness dimension (bottom) --- - bottom_thickness_dim_x = bottom_flange.right() + self._dim_gap + self._arrow_size * 1.5 - painter.drawLine(QPointF(bottom_flange.right(), bottom_flange.top()), QPointF(bottom_thickness_dim_x, bottom_flange.top())) - painter.drawLine(QPointF(bottom_flange.right(), bottom_flange.bottom()), QPointF(bottom_thickness_dim_x, bottom_flange.bottom())) + # --- Bottom flange thickness dimension --- + bft_color = self._set_dimension_pen(painter, "bft") + bottom_thickness_dim_x = bottom_flange.left() - self._dim_gap + painter.drawLine(QPointF(bottom_flange.left(), bottom_flange.top()), QPointF(bottom_thickness_dim_x, bottom_flange.top())) + painter.drawLine(QPointF(bottom_flange.left(), bottom_flange.bottom()), QPointF(bottom_thickness_dim_x, bottom_flange.bottom())) self._draw_dimension_line( painter, QPointF(bottom_thickness_dim_x, bottom_flange.top()), QPointF(bottom_thickness_dim_x, bottom_flange.bottom()), + bft_color, ) self._draw_label( painter, - f"Bottom flange thickness: {self._format_mm(bottom_thickness)}", - QPointF(bottom_thickness_dim_x + 8, bottom_flange.center().y()), - Qt.AlignLeft | Qt.AlignVCenter, + f"BFT: {self._format_mm(bottom_thickness)}", + QPointF(bottom_thickness_dim_x - 4, bottom_flange.center().y()), + Qt.AlignRight | Qt.AlignVCenter, + with_background=False, + color=bft_color, ) # --- Overall depth dimension --- - depth_dim_x = bottom_thickness_dim_x + self._dim_gap + self._arrow_size * 2.0 + depth_color = self._set_dimension_pen(painter, "d") + depth_dim_x = bottom_flange.right() + self._dim_gap painter.drawLine(QPointF(bottom_flange.right(), top_flange.top()), QPointF(depth_dim_x, top_flange.top())) painter.drawLine(QPointF(bottom_flange.right(), bottom_flange.bottom()), QPointF(depth_dim_x, bottom_flange.bottom())) self._draw_dimension_line( painter, QPointF(depth_dim_x, top_flange.top()), QPointF(depth_dim_x, bottom_flange.bottom()), + depth_color, ) self._draw_label( painter, - f"Depth: {self._format_mm(depth)}", + f"D: {self._format_mm(depth)}", QPointF(depth_dim_x + 10, (top_flange.top() + bottom_flange.bottom()) / 2.0), Qt.AlignLeft | Qt.AlignVCenter, + with_background=False, + color=depth_color, ) # --- Web thickness dimension --- + wt_color = self._set_dimension_pen(painter, "wt") mid_y = web.center().y() - self._draw_dimension_line( - painter, - QPointF(web.left(), mid_y), - QPointF(web.right(), mid_y), - ) - painter.drawLine(QPointF(web.left(), mid_y), QPointF(web.left(), mid_y + self._dim_gap * 0.7)) - painter.drawLine(QPointF(web.right(), mid_y), QPointF(web.right(), mid_y + self._dim_gap * 0.7)) + right_anchor = QPointF(web.right(), mid_y) + left_anchor = QPointF(web.left(), mid_y) + self._draw_dimension_line(painter, left_anchor, right_anchor, wt_color) + painter.drawLine(right_anchor, QPointF(right_anchor.x(), right_anchor.y() + self._dim_gap * 0.7)) + painter.drawLine(left_anchor, QPointF(left_anchor.x(), left_anchor.y() + self._dim_gap * 0.7)) self._draw_label( painter, - f"Web thickness: {self._format_mm(web_thickness)}", - QPointF(web.center().x(), mid_y + self._dim_gap * 0.7 + 6), - Qt.AlignHCenter | Qt.AlignTop, + f"WT: {self._format_mm(web_thickness)}", + QPointF(left_anchor.x() - 6, mid_y + self._dim_gap * 0.2), + Qt.AlignRight | Qt.AlignBottom, + with_background=False, + color=wt_color, ) # ------------------------------------------------------------------ @@ -284,7 +307,15 @@ def _draw_placeholder(self, painter: QPainter) -> None: painter.drawText(self.rect(), Qt.AlignCenter, "Select a rolled section to preview") painter.restore() - def _draw_dimension_line(self, painter: QPainter, start: QPointF, end: QPointF) -> None: + def _set_dimension_pen(self, painter: QPainter, key: str) -> QColor: + color = self._dimension_palette.get(key, self._dimension_color) + pen = QPen(color, 1.6) + pen.setCosmetic(True) + pen.setCapStyle(Qt.FlatCap) + painter.setPen(pen) + return color + + def _draw_dimension_line(self, painter: QPainter, start: QPointF, end: QPointF, color: QColor) -> None: direction = QPointF(end.x() - start.x(), end.y() - start.y()) length = math.hypot(direction.x(), direction.y()) if length == 0: @@ -292,10 +323,10 @@ def _draw_dimension_line(self, painter: QPainter, start: QPointF, end: QPointF) unit = QPointF(direction.x() / length, direction.y() / length) offset = unit * (self._arrow_size * 0.7) painter.drawLine(start + offset, end - offset) - self._draw_arrow_head(painter, start, direction) - self._draw_arrow_head(painter, end, QPointF(-direction.x(), -direction.y())) + self._draw_arrow_head(painter, start, direction, color) + self._draw_arrow_head(painter, end, QPointF(-direction.x(), -direction.y()), color) - def _draw_arrow_head(self, painter: QPainter, tip: QPointF, direction: QPointF) -> None: + def _draw_arrow_head(self, painter: QPainter, tip: QPointF, direction: QPointF, color: QColor) -> None: length = math.hypot(direction.x(), direction.y()) if length == 0: return @@ -306,12 +337,21 @@ def _draw_arrow_head(self, painter: QPainter, tip: QPointF, direction: QPointF) left = base + normal * (arrow * 0.45) right = base - normal * (arrow * 0.45) painter.save() - painter.setBrush(self._dimension_color) + painter.setBrush(color) painter.setPen(Qt.NoPen) painter.drawPolygon([tip, left, right]) painter.restore() - def _draw_label(self, painter: QPainter, text: str, anchor: QPointF, align: Qt.Alignment) -> None: + def _draw_label( + self, + painter: QPainter, + text: str, + anchor: QPointF, + align: Qt.Alignment, + *, + with_background: bool = True, + color: Optional[QColor] = None, + ) -> None: metrics = QFontMetricsF(painter.font()) text_rect = metrics.boundingRect(text).adjusted(-6, -3, 6, 3) rect = QRectF(0, 0, text_rect.width(), text_rect.height()) @@ -330,14 +370,16 @@ def _draw_label(self, painter: QPainter, text: str, anchor: QPointF, align: Qt.A else: rect.moveCenter(QPointF(rect.center().x(), anchor.y())) - painter.save() - painter.setPen(Qt.NoPen) - painter.setBrush(self._label_bg) - painter.drawRoundedRect(rect, 4, 4) - painter.restore() + text_color = color or self._text_color + if with_background: + painter.save() + painter.setPen(Qt.NoPen) + painter.setBrush(self._label_bg) + painter.drawRoundedRect(rect, 4, 4) + painter.restore() painter.save() - painter.setPen(self._text_color) + painter.setPen(text_color) painter.drawText(rect, Qt.AlignCenter, text) painter.restore() From 5d0280cdafacc6258da7ec5ef170a0702158fc0c Mon Sep 17 00:00:00 2001 From: Sreejesh06 Date: Mon, 22 Dec 2025 08:45:44 +0530 Subject: [PATCH 53/59] Refactor girder details tab and enhance rolled section preview with improved dimension labeling and styling --- .../desktop/ui/dialogs/additional_inputs.py | 50 +++- .../ui/utils/rolled_section_preview.py | 230 +++++++++++++----- 2 files changed, 208 insertions(+), 72 deletions(-) diff --git a/src/osdagbridge/desktop/ui/dialogs/additional_inputs.py b/src/osdagbridge/desktop/ui/dialogs/additional_inputs.py index 3d2886fe..612e53ad 100644 --- a/src/osdagbridge/desktop/ui/dialogs/additional_inputs.py +++ b/src/osdagbridge/desktop/ui/dialogs/additional_inputs.py @@ -1533,28 +1533,64 @@ def _build_section_card(self): row = self._add_box_row(inputs_grid, row, "Symmetry:", self.symmetry_combo) self.total_depth_input = self._create_line_edit() - row = self._add_box_row(inputs_grid, row, "Total Depth (mm):", self.total_depth_input, self.welded_rows) + row = self._add_box_row( + inputs_grid, + row, + "Total Depth (d, mm):", + self.total_depth_input, + self.welded_rows, + ) self.web_thickness_combo = QComboBox() self.web_thickness_combo.addItems(["All", "Custom"]) apply_field_style(self.web_thickness_combo) - row = self._add_box_row(inputs_grid, row, "Web Thickness (mm):", self.web_thickness_combo, self.welded_rows) + row = self._add_box_row( + inputs_grid, + row, + "Web Thickness (wt, mm):", + self.web_thickness_combo, + self.welded_rows, + ) self.top_width_input = self._create_line_edit() - row = self._add_box_row(inputs_grid, row, "Width of Top Flange (mm):", self.top_width_input, self.welded_rows) + row = self._add_box_row( + inputs_grid, + row, + "Width of Top Flange (tfw, mm):", + self.top_width_input, + self.welded_rows, + ) self.top_thickness_combo = QComboBox() self.top_thickness_combo.addItems(["All", "Custom"]) apply_field_style(self.top_thickness_combo) - row = self._add_box_row(inputs_grid, row, "Top Flange Thickness (mm):", self.top_thickness_combo, self.welded_rows) + row = self._add_box_row( + inputs_grid, + row, + "Top Flange Thickness (tft, mm):", + self.top_thickness_combo, + self.welded_rows, + ) self.bottom_width_input = self._create_line_edit() - row = self._add_box_row(inputs_grid, row, "Width of Bottom Flange (mm):", self.bottom_width_input, self.welded_rows) + row = self._add_box_row( + inputs_grid, + row, + "Width of Bottom Flange (bfw, mm):", + self.bottom_width_input, + self.welded_rows, + ) self.bottom_thickness_combo = QComboBox() self.bottom_thickness_combo.addItems(["All", "Custom"]) apply_field_style(self.bottom_thickness_combo) - row = self._add_box_row(inputs_grid, row, "Bottom Flange Thickness (mm):", self.bottom_thickness_combo, self.welded_rows) + row = self._add_box_row( + inputs_grid, + row, + "Bottom Flange Thickness (bft, mm):", + self.bottom_thickness_combo, + self.welded_rows, + ) self.is_section_combo = QComboBox() self._populate_rolled_section_combo() @@ -1620,7 +1656,7 @@ def _build_section_card(self): self.preview_caption = QLabel("Provide girder inputs to preview") self.preview_caption.setAlignment(Qt.AlignCenter) self.preview_caption.setStyleSheet( - "QLabel { font-size: 10px; color: #5b5b5b; border: none; padding-top: 4px; }" + "QLabel { font-size: 13px; font-weight: 700; color: #1e1e1e; border: none; padding-top: 6px; font-family: 'Ubuntu Sans', 'Segoe UI', sans-serif; }" ) image_layout.addWidget(self.preview_caption) diff --git a/src/osdagbridge/desktop/ui/utils/rolled_section_preview.py b/src/osdagbridge/desktop/ui/utils/rolled_section_preview.py index 34253225..863648b9 100644 --- a/src/osdagbridge/desktop/ui/utils/rolled_section_preview.py +++ b/src/osdagbridge/desktop/ui/utils/rolled_section_preview.py @@ -6,12 +6,16 @@ from typing import Dict, Optional from PySide6.QtCore import QPointF, QRectF, Qt -from PySide6.QtGui import QColor, QFont, QFontMetricsF, QPainter, QPaintEvent, QPen +from PySide6.QtGui import QColor, QFont, QFontMetricsF, QPainter, QPaintEvent, QPen, QTextDocument from PySide6.QtWidgets import QSizePolicy, QWidget from osdagbridge.core.bridge_components.super_structure.girder.properties import BeamSection +OSDAG_BRAND_GREEN = QColor("#90AF13") +OSDAG_FONT_FAMILY = "Ubuntu Sans" + + class RolledSectionPreview(QWidget): """Render a rolled section with CAD-style dimension annotations.""" @@ -22,21 +26,17 @@ def __init__(self, parent: Optional[QWidget] = None) -> None: self._outline_color = QColor("#1b1b1b") self._outline_width = 3.0 - self._dimension_color = QColor("#1e88ff") - self._dimension_palette = { - "tfw": QColor("#1e88ff"), - "tft": QColor("#00a152"), - "bfw": QColor("#ff8f00"), - "bft": QColor("#f4511e"), - "d": QColor("#5e35b1"), - "wt": QColor("#00838f"), - } + self._brand_color = QColor(OSDAG_BRAND_GREEN) + self._dimension_color = QColor(OSDAG_BRAND_GREEN) + self._dimension_keys = ("tfw", "tft", "bfw", "bft", "d", "wt") + self._dimension_palette = {key: QColor(OSDAG_BRAND_GREEN) for key in self._dimension_keys} self._label_bg = QColor(255, 255, 255, 230) self._text_color = QColor("#0f0f0f") + self._brand_font_family = OSDAG_FONT_FAMILY self._outer_margin = 16 self._annotation_margin_top = 36 - self._annotation_margin_bottom = 26 + self._annotation_margin_bottom = 28 self._annotation_margin_left = 52 self._annotation_margin_right = 74 self._dim_gap = 12 @@ -171,6 +171,7 @@ def paintEvent(self, event: QPaintEvent) -> None: # noqa: D401 - Qt override painter.restore() font = QFont(self.font()) + font.setFamily(self._brand_font_family) font.setPointSizeF(max(9.0, font.pointSizeF())) painter.setFont(font) @@ -187,7 +188,7 @@ def paintEvent(self, event: QPaintEvent) -> None: # noqa: D401 - Qt override ) self._draw_label( painter, - f"TFW: {self._format_mm(top_width)}", + self._format_label_markup("tfw", top_width), QPointF(top_flange.center().x(), width_dim_y - 6), Qt.AlignHCenter | Qt.AlignBottom, with_background=False, @@ -196,22 +197,15 @@ def paintEvent(self, event: QPaintEvent) -> None: # noqa: D401 - Qt override # --- Top flange thickness dimension --- tft_color = self._set_dimension_pen(painter, "tft") - flange_dim_x = top_flange.left() - self._dim_gap - painter.drawLine(QPointF(top_flange.left(), top_flange.top()), QPointF(flange_dim_x, top_flange.top())) - painter.drawLine(QPointF(top_flange.left(), top_flange.bottom()), QPointF(flange_dim_x, top_flange.bottom())) - self._draw_dimension_line( + self._draw_vertical_thickness_dimension( painter, - QPointF(flange_dim_x, top_flange.top()), - QPointF(flange_dim_x, top_flange.bottom()), + top_flange.left(), + top_flange.top(), + top_flange.bottom(), + top_thickness, tft_color, - ) - self._draw_label( - painter, - f"TFT: {self._format_mm(top_thickness)}", - QPointF(flange_dim_x - 4, top_flange.center().y()), - Qt.AlignRight | Qt.AlignVCenter, - with_background=False, - color=tft_color, + label_symbol="tft", + label_align=Qt.AlignRight | Qt.AlignVCenter, ) # --- Bottom flange width dimension --- @@ -227,7 +221,7 @@ def paintEvent(self, event: QPaintEvent) -> None: # noqa: D401 - Qt override ) self._draw_label( painter, - f"BFW: {self._format_mm(bottom_width)}", + self._format_label_markup("bfw", bottom_width), QPointF(bottom_flange.center().x(), bottom_width_dim_y + 6), Qt.AlignHCenter | Qt.AlignTop, with_background=False, @@ -236,22 +230,15 @@ def paintEvent(self, event: QPaintEvent) -> None: # noqa: D401 - Qt override # --- Bottom flange thickness dimension --- bft_color = self._set_dimension_pen(painter, "bft") - bottom_thickness_dim_x = bottom_flange.left() - self._dim_gap - painter.drawLine(QPointF(bottom_flange.left(), bottom_flange.top()), QPointF(bottom_thickness_dim_x, bottom_flange.top())) - painter.drawLine(QPointF(bottom_flange.left(), bottom_flange.bottom()), QPointF(bottom_thickness_dim_x, bottom_flange.bottom())) - self._draw_dimension_line( + self._draw_vertical_thickness_dimension( painter, - QPointF(bottom_thickness_dim_x, bottom_flange.top()), - QPointF(bottom_thickness_dim_x, bottom_flange.bottom()), + bottom_flange.left(), + bottom_flange.top(), + bottom_flange.bottom(), + bottom_thickness, bft_color, - ) - self._draw_label( - painter, - f"BFT: {self._format_mm(bottom_thickness)}", - QPointF(bottom_thickness_dim_x - 4, bottom_flange.center().y()), - Qt.AlignRight | Qt.AlignVCenter, - with_background=False, - color=bft_color, + label_symbol="bft", + label_align=Qt.AlignRight | Qt.AlignVCenter, ) # --- Overall depth dimension --- @@ -267,7 +254,7 @@ def paintEvent(self, event: QPaintEvent) -> None: # noqa: D401 - Qt override ) self._draw_label( painter, - f"D: {self._format_mm(depth)}", + self._format_label_markup("d", depth), QPointF(depth_dim_x + 10, (top_flange.top() + bottom_flange.bottom()) / 2.0), Qt.AlignLeft | Qt.AlignVCenter, with_background=False, @@ -276,21 +263,17 @@ def paintEvent(self, event: QPaintEvent) -> None: # noqa: D401 - Qt override # --- Web thickness dimension --- wt_color = self._set_dimension_pen(painter, "wt") - mid_y = web.center().y() - right_anchor = QPointF(web.right(), mid_y) - left_anchor = QPointF(web.left(), mid_y) - self._draw_dimension_line(painter, left_anchor, right_anchor, wt_color) - painter.drawLine(right_anchor, QPointF(right_anchor.x(), right_anchor.y() + self._dim_gap * 0.7)) - painter.drawLine(left_anchor, QPointF(left_anchor.x(), left_anchor.y() + self._dim_gap * 0.7)) - self._draw_label( + self._draw_web_thickness_dimension( painter, - f"WT: {self._format_mm(web_thickness)}", - QPointF(left_anchor.x() - 6, mid_y + self._dim_gap * 0.2), - Qt.AlignRight | Qt.AlignBottom, - with_background=False, - color=wt_color, + web.left(), + web.right(), + web.center().y(), + web_thickness, + wt_color, + label_symbol="wt", ) + # ------------------------------------------------------------------ # Drawing helpers # ------------------------------------------------------------------ @@ -302,6 +285,7 @@ def _draw_placeholder(self, painter: QPainter) -> None: painter.drawRect(self.rect().adjusted(12, 12, -12, -12)) painter.setPen(QColor("#6f6f6f")) font = QFont(self.font()) + font.setFamily(self._brand_font_family) font.setPointSizeF(max(font.pointSizeF(), 10.0)) painter.setFont(font) painter.drawText(self.rect(), Qt.AlignCenter, "Select a rolled section to preview") @@ -315,16 +299,102 @@ def _set_dimension_pen(self, painter: QPainter, key: str) -> QColor: painter.setPen(pen) return color - def _draw_dimension_line(self, painter: QPainter, start: QPointF, end: QPointF, color: QColor) -> None: + def _draw_vertical_thickness_dimension( + self, + painter: QPainter, + flange_edge_x: float, + top_y: float, + bottom_y: float, + thickness: Optional[float], + color: QColor, + *, + label_symbol: str, + label_align: Qt.Alignment, + ) -> None: + extension = self._dim_gap * 0.9 + arrow_length = max(self._dim_gap * 1.2, self._arrow_size * 1.4) + dimension_x = flange_edge_x - extension + top_extension = QPointF(dimension_x, top_y) + bottom_extension = QPointF(dimension_x, bottom_y) + + painter.drawLine(QPointF(flange_edge_x, top_y), top_extension) + painter.drawLine(QPointF(flange_edge_x, bottom_y), bottom_extension) + painter.drawLine(top_extension, bottom_extension) + + painter.drawLine(QPointF(dimension_x, top_y - arrow_length), top_extension) + painter.drawLine(bottom_extension, QPointF(dimension_x, bottom_y + arrow_length)) + self._draw_arrow_head(painter, top_extension, QPointF(0, -1), color) + self._draw_arrow_head(painter, bottom_extension, QPointF(0, 1), color) + + label_anchor = QPointF(dimension_x - self._dim_gap * 0.4, (top_y + bottom_y) / 2.0) + self._draw_label( + painter, + self._format_label_markup(label_symbol, thickness), + label_anchor, + label_align, + with_background=False, + color=color, + ) + + def _draw_web_thickness_dimension( + self, + painter: QPainter, + left_x: float, + right_x: float, + mid_y: float, + thickness: Optional[float], + color: QColor, + *, + label_symbol: str, + ) -> None: + extension = self._dim_gap * 0.7 + arrow_length = max(self._dim_gap * 1.2, self._arrow_size * 1.4) + + painter.drawLine(QPointF(left_x, mid_y - extension), QPointF(left_x, mid_y + extension)) + painter.drawLine(QPointF(right_x, mid_y - extension), QPointF(right_x, mid_y + extension)) + + painter.drawLine(QPointF(left_x - arrow_length, mid_y), QPointF(left_x, mid_y)) + painter.drawLine(QPointF(right_x, mid_y), QPointF(right_x + arrow_length, mid_y)) + self._draw_arrow_head(painter, QPointF(left_x, mid_y), QPointF(-1, 0), color) + self._draw_arrow_head(painter, QPointF(right_x, mid_y), QPointF(1, 0), color) + + label_offset = self._dim_gap * 0.6 + label_anchor = QPointF(left_x - arrow_length - label_offset, mid_y) + self._draw_label( + painter, + self._format_label_markup(label_symbol, thickness), + label_anchor, + Qt.AlignRight | Qt.AlignVCenter, + with_background=False, + color=color, + ) + + def _draw_dimension_line( + self, + painter: QPainter, + start: QPointF, + end: QPointF, + color: QColor, + *, + external: bool = False, + ) -> None: direction = QPointF(end.x() - start.x(), end.y() - start.y()) length = math.hypot(direction.x(), direction.y()) if length == 0: return unit = QPointF(direction.x() / length, direction.y() / length) - offset = unit * (self._arrow_size * 0.7) - painter.drawLine(start + offset, end - offset) - self._draw_arrow_head(painter, start, direction, color) - self._draw_arrow_head(painter, end, QPointF(-direction.x(), -direction.y()), color) + if external: + painter.drawLine(start, end) + outward = self._arrow_size * 0.9 + outer_start = QPointF(start.x() - unit.x() * outward, start.y() - unit.y() * outward) + outer_end = QPointF(end.x() + unit.x() * outward, end.y() + unit.y() * outward) + self._draw_arrow_head(painter, outer_start, direction, color) + self._draw_arrow_head(painter, outer_end, QPointF(-direction.x(), -direction.y()), color) + else: + offset = unit * (self._arrow_size * 0.7) + painter.drawLine(start + offset, end - offset) + self._draw_arrow_head(painter, start, direction, color) + self._draw_arrow_head(painter, end, QPointF(-direction.x(), -direction.y()), color) def _draw_arrow_head(self, painter: QPainter, tip: QPointF, direction: QPointF, color: QColor) -> None: length = math.hypot(direction.x(), direction.y()) @@ -352,9 +422,16 @@ def _draw_label( with_background: bool = True, color: Optional[QColor] = None, ) -> None: - metrics = QFontMetricsF(painter.font()) - text_rect = metrics.boundingRect(text).adjusted(-6, -3, 6, 3) - rect = QRectF(0, 0, text_rect.width(), text_rect.height()) + text_color = color or self._text_color + html_text = text if color is None else f'{text}' + + doc = QTextDocument() + doc.setDefaultFont(painter.font()) + doc.setHtml(html_text) + text_size = doc.size() + padding_x = 6 + padding_y = 4 + rect = QRectF(0, 0, text_size.width() + padding_x * 2, text_size.height() + padding_y * 2) if align & Qt.AlignLeft: rect.moveLeft(anchor.x()) @@ -370,7 +447,13 @@ def _draw_label( else: rect.moveCenter(QPointF(rect.center().x(), anchor.y())) - text_color = color or self._text_color + content_rect = QRectF( + rect.left() + padding_x, + rect.top() + padding_y, + text_size.width(), + text_size.height(), + ) + if with_background: painter.save() painter.setPen(Qt.NoPen) @@ -379,10 +462,27 @@ def _draw_label( painter.restore() painter.save() - painter.setPen(text_color) - painter.drawText(rect, Qt.AlignCenter, text) + painter.translate(content_rect.topLeft()) + doc.drawContents(painter) painter.restore() + def _format_label_markup(self, symbol: str, value: Optional[float] = None) -> str: + formatted_symbol = self._format_symbol_markup(symbol) + if value is None: + return formatted_symbol + return f"{formatted_symbol} = {self._format_mm(value)}" + + @staticmethod + def _format_symbol_markup(symbol: str) -> str: + clean = symbol.lower().strip() + if len(clean) <= 1: + return clean or symbol + return f"{clean[0]}{clean[1:]}" + + @staticmethod + def _color_to_hex(color: QColor) -> str: + return color.name(QColor.HexRgb) + @staticmethod def _format_mm(value: float) -> str: rounded = round(value) From 1c1542e8d3d9fcb330e9570fb24dbd0c502cc6f1 Mon Sep 17 00:00:00 2001 From: Sreejesh06 Date: Mon, 29 Dec 2025 14:54:52 +0530 Subject: [PATCH 54/59] Add support for weld visibility toggle in RolledSectionPreview and adjust dimension rendering --- .../desktop/ui/dialogs/additional_inputs.py | 1 + .../ui/utils/rolled_section_preview.py | 318 +++++++++++++++--- 2 files changed, 265 insertions(+), 54 deletions(-) diff --git a/src/osdagbridge/desktop/ui/dialogs/additional_inputs.py b/src/osdagbridge/desktop/ui/dialogs/additional_inputs.py index 612e53ad..b55ab130 100644 --- a/src/osdagbridge/desktop/ui/dialogs/additional_inputs.py +++ b/src/osdagbridge/desktop/ui/dialogs/additional_inputs.py @@ -1846,6 +1846,7 @@ def _update_preview(self): web_thickness_mm=dims["web_thickness_mm"], flange_thickness_mm=dims["top_flange_thickness_mm"], bottom_flange_thickness_mm=dims["bottom_flange_thickness_mm"], + show_welds=True, ) else: self.section_preview.clear() diff --git a/src/osdagbridge/desktop/ui/utils/rolled_section_preview.py b/src/osdagbridge/desktop/ui/utils/rolled_section_preview.py index 863648b9..b933c30a 100644 --- a/src/osdagbridge/desktop/ui/utils/rolled_section_preview.py +++ b/src/osdagbridge/desktop/ui/utils/rolled_section_preview.py @@ -6,7 +6,7 @@ from typing import Dict, Optional from PySide6.QtCore import QPointF, QRectF, Qt -from PySide6.QtGui import QColor, QFont, QFontMetricsF, QPainter, QPaintEvent, QPen, QTextDocument +from PySide6.QtGui import QColor, QFont, QFontMetricsF, QPainter, QPaintEvent, QPainterPath, QPen, QTextDocument from PySide6.QtWidgets import QSizePolicy, QWidget from osdagbridge.core.bridge_components.super_structure.girder.properties import BeamSection @@ -33,6 +33,7 @@ def __init__(self, parent: Optional[QWidget] = None) -> None: self._label_bg = QColor(255, 255, 255, 230) self._text_color = QColor("#0f0f0f") self._brand_font_family = OSDAG_FONT_FAMILY + self._show_welds = False self._outer_margin = 16 self._annotation_margin_top = 36 @@ -42,6 +43,13 @@ def __init__(self, parent: Optional[QWidget] = None) -> None: self._dim_gap = 12 self._arrow_size = 9 + # Minimum radii keep rolled sections visibly curved even when the + # catalogue omits R1/R2; welded previews stay sharp by skipping these. + self._min_root_radius_px = 6.0 + self._min_toe_radius_px = 4.0 + self._root_radius_ratio = 0.45 + self._toe_radius_ratio = 0.35 + self.setMinimumSize(360, 260) self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) @@ -62,7 +70,10 @@ def set_section(self, section: Optional[BeamSection]) -> None: "web_thickness": float(section.web_thickness_mm), "top_flange_thickness": float(section.flange_thickness_mm), "bottom_flange_thickness": float(section.flange_thickness_mm), + "root_radius_mm": float(section.root_radius_r1_mm or 0.0), + "toe_radius_mm": float(section.root_radius_r2_mm or 0.0), } + self._show_welds = False self.update() def set_dimensions( @@ -74,6 +85,7 @@ def set_dimensions( flange_thickness_mm: float, bottom_flange_width_mm: Optional[float] = None, bottom_flange_thickness_mm: Optional[float] = None, + show_welds: bool = False, ) -> None: """Feed custom dimensions (e.g., welded sections) directly.""" @@ -85,7 +97,10 @@ def set_dimensions( "web_thickness": float(web_thickness_mm), "top_flange_thickness": float(flange_thickness_mm), "bottom_flange_thickness": float(bottom_flange_thickness_mm or flange_thickness_mm), + "root_radius_mm": 0.0, + "toe_radius_mm": 0.0, } + self._show_welds = show_welds self.update() def clear(self) -> None: @@ -93,6 +108,7 @@ def clear(self) -> None: self._section = None self._dimensions = {} + self._show_welds = False self.update() # ------------------------------------------------------------------ @@ -160,16 +176,32 @@ def paintEvent(self, event: QPaintEvent) -> None: # noqa: D401 - Qt override bottom_height, ) + root_radius_px = max(0.0, float(dims.get("root_radius_mm", 0.0)) * scale) + toe_radius_px = max(0.0, float(dims.get("toe_radius_mm", 0.0)) * scale) + section_path = self._build_section_path( + top_flange, + web, + bottom_flange, + root_radius_px, + toe_radius_px, + ) + painter.save() outline_pen = QPen(self._outline_color, self._outline_width) outline_pen.setJoinStyle(Qt.MiterJoin) painter.setPen(outline_pen) painter.setBrush(QColor("#fefefe")) - painter.drawRect(top_flange) - painter.drawRect(web) - painter.drawRect(bottom_flange) + if section_path is not None: + painter.drawPath(section_path) + else: + painter.drawRect(top_flange) + painter.drawRect(web) + painter.drawRect(bottom_flange) painter.restore() + if self._show_welds: + self._draw_welds(painter, top_flange, web, bottom_flange) + font = QFont(self.font()) font.setFamily(self._brand_font_family) font.setPointSizeF(max(9.0, font.pointSizeF())) @@ -177,13 +209,15 @@ def paintEvent(self, event: QPaintEvent) -> None: # noqa: D401 - Qt override # --- Top flange width dimension --- tfw_color = self._set_dimension_pen(painter, "tfw") - width_dim_y = top_flange.top() - self._dim_gap - painter.drawLine(QPointF(top_flange.left(), top_flange.top()), QPointF(top_flange.left(), width_dim_y)) - painter.drawLine(QPointF(top_flange.right(), top_flange.top()), QPointF(top_flange.right(), width_dim_y)) + width_dim_y = self._snap_coordinate(top_flange.top() - self._dim_gap) + left_extension_end = QPointF(self._snap_coordinate(top_flange.left()), width_dim_y) + right_extension_end = QPointF(self._snap_coordinate(top_flange.right()), width_dim_y) + painter.drawLine(QPointF(left_extension_end.x(), top_flange.top()), left_extension_end) + painter.drawLine(QPointF(right_extension_end.x(), top_flange.top()), right_extension_end) self._draw_dimension_line( painter, - QPointF(top_flange.left(), width_dim_y), - QPointF(top_flange.right(), width_dim_y), + left_extension_end, + right_extension_end, tfw_color, ) self._draw_label( @@ -210,13 +244,15 @@ def paintEvent(self, event: QPaintEvent) -> None: # noqa: D401 - Qt override # --- Bottom flange width dimension --- bfw_color = self._set_dimension_pen(painter, "bfw") - bottom_width_dim_y = bottom_flange.bottom() + self._dim_gap - painter.drawLine(QPointF(bottom_flange.left(), bottom_flange.bottom()), QPointF(bottom_flange.left(), bottom_width_dim_y)) - painter.drawLine(QPointF(bottom_flange.right(), bottom_flange.bottom()), QPointF(bottom_flange.right(), bottom_width_dim_y)) + bottom_width_dim_y = self._snap_coordinate(bottom_flange.bottom() + self._dim_gap) + bottom_left_extension = QPointF(self._snap_coordinate(bottom_flange.left()), bottom_width_dim_y) + bottom_right_extension = QPointF(self._snap_coordinate(bottom_flange.right()), bottom_width_dim_y) + painter.drawLine(QPointF(bottom_left_extension.x(), bottom_flange.bottom()), bottom_left_extension) + painter.drawLine(QPointF(bottom_right_extension.x(), bottom_flange.bottom()), bottom_right_extension) self._draw_dimension_line( painter, - QPointF(bottom_flange.left(), bottom_width_dim_y), - QPointF(bottom_flange.right(), bottom_width_dim_y), + bottom_left_extension, + bottom_right_extension, bfw_color, ) self._draw_label( @@ -243,13 +279,16 @@ def paintEvent(self, event: QPaintEvent) -> None: # noqa: D401 - Qt override # --- Overall depth dimension --- depth_color = self._set_dimension_pen(painter, "d") - depth_dim_x = bottom_flange.right() + self._dim_gap - painter.drawLine(QPointF(bottom_flange.right(), top_flange.top()), QPointF(depth_dim_x, top_flange.top())) - painter.drawLine(QPointF(bottom_flange.right(), bottom_flange.bottom()), QPointF(depth_dim_x, bottom_flange.bottom())) + anchor_x = self._snap_coordinate(bottom_flange.right()) + depth_dim_x = self._snap_coordinate(anchor_x + self._dim_gap) + top_depth_extension = QPointF(depth_dim_x, self._snap_coordinate(top_flange.top())) + bottom_depth_extension = QPointF(depth_dim_x, self._snap_coordinate(bottom_flange.bottom())) + painter.drawLine(QPointF(anchor_x, top_depth_extension.y()), top_depth_extension) + painter.drawLine(QPointF(anchor_x, bottom_depth_extension.y()), bottom_depth_extension) self._draw_dimension_line( painter, - QPointF(depth_dim_x, top_flange.top()), - QPointF(depth_dim_x, bottom_flange.bottom()), + top_depth_extension, + bottom_depth_extension, depth_color, ) self._draw_label( @@ -277,6 +316,189 @@ def paintEvent(self, event: QPaintEvent) -> None: # noqa: D401 - Qt override # ------------------------------------------------------------------ # Drawing helpers # ------------------------------------------------------------------ + def _build_section_path( + self, + top_flange: QRectF, + web: QRectF, + bottom_flange: QRectF, + root_radius: float, + toe_radius: float, + ) -> Optional[QPainterPath]: + if min(top_flange.width(), bottom_flange.width(), web.width()) <= 0: + return None + + tf_left, tf_right = top_flange.left(), top_flange.right() + tf_top, tf_bottom = top_flange.top(), top_flange.bottom() + bf_left, bf_right = bottom_flange.left(), bottom_flange.right() + bf_top, bf_bottom = bottom_flange.top(), bottom_flange.bottom() + web_left, web_right = web.left(), web.right() + + top_toe = self._effective_toe_radius_px(toe_radius, top_flange) + bottom_toe = self._effective_toe_radius_px(toe_radius, bottom_flange) + top_root = self._effective_root_radius_px(root_radius, top_flange, web) + bottom_root = self._effective_root_radius_px(root_radius, bottom_flange, web) + + path = QPainterPath() + path.moveTo(tf_left + top_toe, tf_top) + path.lineTo(tf_right - top_toe, tf_top) + if top_toe > 0: + rect = QRectF(tf_right - 2 * top_toe, tf_top, 2 * top_toe, 2 * top_toe) + path.arcTo(rect, 90, -90) + else: + path.lineTo(tf_right, tf_top) + + path.lineTo(tf_right, tf_bottom) + + if top_root > 0: + path.lineTo(web_right + top_root, tf_bottom) + # Root fillet: curve DOWN onto the web (adding material in the corner) + rect = QRectF(web_right, tf_bottom, 2 * top_root, 2 * top_root) + path.arcTo(rect, 90, 90) + else: + path.lineTo(web_right, tf_bottom) + + path.lineTo(web_right, bf_top - bottom_root) + if bottom_root > 0: + # Root fillet: curve OUT onto the bottom flange + rect = QRectF(web_right, bf_top - 2 * bottom_root, 2 * bottom_root, 2 * bottom_root) + path.arcTo(rect, 180, 90) + else: + path.lineTo(web_right, bf_top) + + path.lineTo(bf_right, bf_top) + path.lineTo(bf_right, bf_bottom - bottom_toe) + if bottom_toe > 0: + rect = QRectF(bf_right - 2 * bottom_toe, bf_bottom - 2 * bottom_toe, 2 * bottom_toe, 2 * bottom_toe) + path.arcTo(rect, 0, -90) + else: + path.lineTo(bf_right, bf_bottom) + + path.lineTo(bf_left + bottom_toe, bf_bottom) + if bottom_toe > 0: + rect = QRectF(bf_left, bf_bottom - 2 * bottom_toe, 2 * bottom_toe, 2 * bottom_toe) + path.arcTo(rect, 270, -90) + else: + path.lineTo(bf_left, bf_bottom) + + path.lineTo(bf_left, bf_top) + + if bottom_root > 0: + path.lineTo(web_left - bottom_root, bf_top) + # Root fillet: curve UP onto the web + rect = QRectF(web_left - 2 * bottom_root, bf_top - 2 * bottom_root, 2 * bottom_root, 2 * bottom_root) + path.arcTo(rect, 270, 90) + else: + path.lineTo(web_left, bf_top) + + path.lineTo(web_left, tf_bottom + top_root) + if top_root > 0: + # Root fillet: curve OUT onto the top flange + rect = QRectF(web_left - 2 * top_root, tf_bottom, 2 * top_root, 2 * top_root) + path.arcTo(rect, 0, 90) + else: + path.lineTo(web_left, tf_bottom) + + path.lineTo(tf_left, tf_bottom) + path.lineTo(tf_left, tf_top + top_toe) + if top_toe > 0: + rect = QRectF(tf_left, tf_top, 2 * top_toe, 2 * top_toe) + path.arcTo(rect, 180, -90) + else: + path.lineTo(tf_left, tf_top) + + path.closeSubpath() + return path + + def _snap_coordinate(self, value: float, *, precision: float = 0.5) -> float: + """Quantize coordinates to reduce anti-alias fuzz on shared anchors.""" + + if not precision or precision <= 0: + return value + return round(value / precision) * precision + + def _effective_toe_radius_px(self, requested: float, flange: QRectF) -> float: + max_radius = max(0.0, min(flange.width(), flange.height()) / 2.0) + if max_radius == 0.0: + return 0.0 + + radius = max(0.0, requested) + if radius <= 0.0 and not self._show_welds: + radius = max(max_radius * self._toe_radius_ratio, self._min_toe_radius_px) + + return self._snap_coordinate(min(radius, max_radius)) + + def _effective_root_radius_px(self, requested: float, flange: QRectF, web: QRectF) -> float: + # Limit root radius to fit within the flange overhang and not exceed half web height + flange_overhang = (flange.width() - web.width()) / 2.0 + max_radius = max(0.0, min(flange_overhang, web.height() / 2.0)) + + if max_radius == 0.0: + return 0.0 + + radius = max(0.0, requested) + if radius <= 0.0 and not self._show_welds: + radius = max(max_radius * self._root_radius_ratio, self._min_root_radius_px) + + return self._snap_coordinate(min(radius, max_radius)) + + def _draw_welds(self, painter: QPainter, top_flange: QRectF, web: QRectF, bottom_flange: QRectF) -> None: + painter.save() + painter.setRenderHint(QPainter.Antialiasing, True) + fill_color = QColor("#0f0f0f") + outline_pen = QPen(QColor("#0f0f0f"), 0.9) + outline_pen.setCosmetic(True) + painter.setBrush(fill_color) + painter.setPen(outline_pen) + + horizontal_leg = max(4.0, min(web.width() * 0.8, 24.0)) + top_vertical_leg = max(4.0, min(top_flange.height() * 0.9, 22.0)) + bottom_vertical_leg = max(4.0, min(bottom_flange.height() * 0.9, 22.0)) + + for sign in (-1, 1): + top_corner_x = web.left() if sign < 0 else web.right() + top_corner = QPointF(top_corner_x, top_flange.bottom()) + painter.drawPath( + self._build_fillet_path( + top_corner, + horizontal_leg, + top_vertical_leg, + horizontal_sign=sign, + vertical_sign=1.0, + ) + ) + + bottom_corner_x = web.left() if sign < 0 else web.right() + bottom_corner = QPointF(bottom_corner_x, bottom_flange.top()) + painter.drawPath( + self._build_fillet_path( + bottom_corner, + horizontal_leg, + bottom_vertical_leg, + horizontal_sign=sign, + vertical_sign=-1.0, + ) + ) + + painter.restore() + + def _build_fillet_path( + self, + corner: QPointF, + horizontal_leg: float, + vertical_leg: float, + *, + horizontal_sign: float, + vertical_sign: float, + ) -> QPainterPath: + leg_x = horizontal_leg * horizontal_sign + leg_y = vertical_leg * vertical_sign + path = QPainterPath(corner) + path.lineTo(QPointF(corner.x() + leg_x, corner.y())) + control = QPointF(corner.x() + leg_x * 0.55, corner.y() + leg_y * 0.55) + path.quadTo(control, QPointF(corner.x(), corner.y() + leg_y)) + path.closeSubpath() + return path + def _draw_placeholder(self, painter: QPainter) -> None: painter.save() pen = QPen(QColor("#b7b7b7"), 1.2, Qt.DashLine) @@ -312,21 +534,23 @@ def _draw_vertical_thickness_dimension( label_align: Qt.Alignment, ) -> None: extension = self._dim_gap * 0.9 - arrow_length = max(self._dim_gap * 1.2, self._arrow_size * 1.4) - dimension_x = flange_edge_x - extension - top_extension = QPointF(dimension_x, top_y) - bottom_extension = QPointF(dimension_x, bottom_y) - - painter.drawLine(QPointF(flange_edge_x, top_y), top_extension) - painter.drawLine(QPointF(flange_edge_x, bottom_y), bottom_extension) + anchor_x = self._snap_coordinate(flange_edge_x) + dimension_x = self._snap_coordinate(anchor_x - extension) + snapped_top_y = self._snap_coordinate(top_y) + snapped_bottom_y = self._snap_coordinate(bottom_y) + top_extension = QPointF(dimension_x, snapped_top_y) + bottom_extension = QPointF(dimension_x, snapped_bottom_y) + + painter.drawLine(QPointF(anchor_x, snapped_top_y), top_extension) + painter.drawLine(QPointF(anchor_x, snapped_bottom_y), bottom_extension) painter.drawLine(top_extension, bottom_extension) - - painter.drawLine(QPointF(dimension_x, top_y - arrow_length), top_extension) - painter.drawLine(bottom_extension, QPointF(dimension_x, bottom_y + arrow_length)) self._draw_arrow_head(painter, top_extension, QPointF(0, -1), color) self._draw_arrow_head(painter, bottom_extension, QPointF(0, 1), color) - label_anchor = QPointF(dimension_x - self._dim_gap * 0.4, (top_y + bottom_y) / 2.0) + label_anchor = QPointF( + dimension_x - self._dim_gap * 0.4, + (snapped_top_y + snapped_bottom_y) / 2.0, + ) self._draw_label( painter, self._format_label_markup(label_symbol, thickness), @@ -347,19 +571,15 @@ def _draw_web_thickness_dimension( *, label_symbol: str, ) -> None: - extension = self._dim_gap * 0.7 - arrow_length = max(self._dim_gap * 1.2, self._arrow_size * 1.4) - - painter.drawLine(QPointF(left_x, mid_y - extension), QPointF(left_x, mid_y + extension)) - painter.drawLine(QPointF(right_x, mid_y - extension), QPointF(right_x, mid_y + extension)) - - painter.drawLine(QPointF(left_x - arrow_length, mid_y), QPointF(left_x, mid_y)) - painter.drawLine(QPointF(right_x, mid_y), QPointF(right_x + arrow_length, mid_y)) - self._draw_arrow_head(painter, QPointF(left_x, mid_y), QPointF(-1, 0), color) - self._draw_arrow_head(painter, QPointF(right_x, mid_y), QPointF(1, 0), color) + snapped_mid_y = self._snap_coordinate(mid_y) + left_point = QPointF(self._snap_coordinate(left_x), snapped_mid_y) + right_point = QPointF(self._snap_coordinate(right_x), snapped_mid_y) + painter.drawLine(left_point, right_point) + self._draw_arrow_head(painter, left_point, QPointF(-1, 0), color) + self._draw_arrow_head(painter, right_point, QPointF(1, 0), color) label_offset = self._dim_gap * 0.6 - label_anchor = QPointF(left_x - arrow_length - label_offset, mid_y) + label_anchor = QPointF(left_point.x() - label_offset, snapped_mid_y) self._draw_label( painter, self._format_label_markup(label_symbol, thickness), @@ -382,19 +602,9 @@ def _draw_dimension_line( length = math.hypot(direction.x(), direction.y()) if length == 0: return - unit = QPointF(direction.x() / length, direction.y() / length) - if external: - painter.drawLine(start, end) - outward = self._arrow_size * 0.9 - outer_start = QPointF(start.x() - unit.x() * outward, start.y() - unit.y() * outward) - outer_end = QPointF(end.x() + unit.x() * outward, end.y() + unit.y() * outward) - self._draw_arrow_head(painter, outer_start, direction, color) - self._draw_arrow_head(painter, outer_end, QPointF(-direction.x(), -direction.y()), color) - else: - offset = unit * (self._arrow_size * 0.7) - painter.drawLine(start + offset, end - offset) - self._draw_arrow_head(painter, start, direction, color) - self._draw_arrow_head(painter, end, QPointF(-direction.x(), -direction.y()), color) + painter.drawLine(start, end) + self._draw_arrow_head(painter, start, direction, color) + self._draw_arrow_head(painter, end, QPointF(-direction.x(), -direction.y()), color) def _draw_arrow_head(self, painter: QPainter, tip: QPointF, direction: QPointF, color: QColor) -> None: length = math.hypot(direction.x(), direction.y()) From b134be0a631dbf5b3c7c605ad5131320fcb0ca74 Mon Sep 17 00:00:00 2001 From: Sreejesh06 Date: Wed, 31 Dec 2025 00:52:40 +0530 Subject: [PATCH 55/59] Add SVG resources and refactor InputDock for dynamic field generation --- .../bridge_types/plate_girder/ui_fields.py | 266 ++- src/osdagbridge/desktop/resources/__init__.py | 3 + .../__pycache__/resources/resources.qrc | 36 + .../__pycache__/resources/resources_rc.py | 1471 +++++++++++++++++ .../resources/themes/darkstyle.qss | 32 + .../resources/themes/lightstyle.qss | 243 +++ .../resources/vectors/Osdag_logo.svg | 15 + .../resources/vectors/arrow_down_dark.svg | 1 + .../resources/vectors/arrow_down_light.svg | 1 + .../resources/vectors/arrow_up_dark.svg | 3 + .../resources/vectors/arrow_up_light.svg | 3 + .../__pycache__/resources/vectors/checked.svg | 1 + .../__pycache__/resources/vectors/design.svg | 1 + .../resources/vectors/design_report.svg | 1 + .../vectors/input_dock_active_light.svg | 3 + .../vectors/input_dock_inactive_light.svg | 1 + .../resources/vectors/inputs_label_light.svg | 4 + .../resources/vectors/lock_close.svg | 1 + .../resources/vectors/lock_open.svg | 1 + .../resources/vectors/lock_open_light.svg | 1 + .../vectors/logs_dock_active_light.svg | 3 + .../vectors/logs_dock_inactive_light.svg | 1 + .../vectors/output_dock_active_light.svg | 3 + .../vectors/output_dock_inactive_light.svg | 1 + .../resources/vectors/outputs_label_light.svg | 4 + .../__pycache__/resources/vectors/save.svg | 1 + .../desktop/ui/docks/input_dock.py | 1040 ++++++------ 27 files changed, 2536 insertions(+), 605 deletions(-) create mode 100644 src/osdagbridge/desktop/resources/__init__.py create mode 100644 src/osdagbridge/desktop/ui/dialogs/__pycache__/resources/resources.qrc create mode 100644 src/osdagbridge/desktop/ui/dialogs/__pycache__/resources/resources_rc.py create mode 100644 src/osdagbridge/desktop/ui/dialogs/__pycache__/resources/themes/darkstyle.qss create mode 100644 src/osdagbridge/desktop/ui/dialogs/__pycache__/resources/themes/lightstyle.qss create mode 100644 src/osdagbridge/desktop/ui/dialogs/__pycache__/resources/vectors/Osdag_logo.svg create mode 100644 src/osdagbridge/desktop/ui/dialogs/__pycache__/resources/vectors/arrow_down_dark.svg create mode 100644 src/osdagbridge/desktop/ui/dialogs/__pycache__/resources/vectors/arrow_down_light.svg create mode 100644 src/osdagbridge/desktop/ui/dialogs/__pycache__/resources/vectors/arrow_up_dark.svg create mode 100644 src/osdagbridge/desktop/ui/dialogs/__pycache__/resources/vectors/arrow_up_light.svg create mode 100644 src/osdagbridge/desktop/ui/dialogs/__pycache__/resources/vectors/checked.svg create mode 100644 src/osdagbridge/desktop/ui/dialogs/__pycache__/resources/vectors/design.svg create mode 100644 src/osdagbridge/desktop/ui/dialogs/__pycache__/resources/vectors/design_report.svg create mode 100644 src/osdagbridge/desktop/ui/dialogs/__pycache__/resources/vectors/input_dock_active_light.svg create mode 100644 src/osdagbridge/desktop/ui/dialogs/__pycache__/resources/vectors/input_dock_inactive_light.svg create mode 100644 src/osdagbridge/desktop/ui/dialogs/__pycache__/resources/vectors/inputs_label_light.svg create mode 100644 src/osdagbridge/desktop/ui/dialogs/__pycache__/resources/vectors/lock_close.svg create mode 100644 src/osdagbridge/desktop/ui/dialogs/__pycache__/resources/vectors/lock_open.svg create mode 100644 src/osdagbridge/desktop/ui/dialogs/__pycache__/resources/vectors/lock_open_light.svg create mode 100644 src/osdagbridge/desktop/ui/dialogs/__pycache__/resources/vectors/logs_dock_active_light.svg create mode 100644 src/osdagbridge/desktop/ui/dialogs/__pycache__/resources/vectors/logs_dock_inactive_light.svg create mode 100644 src/osdagbridge/desktop/ui/dialogs/__pycache__/resources/vectors/output_dock_active_light.svg create mode 100644 src/osdagbridge/desktop/ui/dialogs/__pycache__/resources/vectors/output_dock_inactive_light.svg create mode 100644 src/osdagbridge/desktop/ui/dialogs/__pycache__/resources/vectors/outputs_label_light.svg create mode 100644 src/osdagbridge/desktop/ui/dialogs/__pycache__/resources/vectors/save.svg diff --git a/src/osdagbridge/core/bridge_types/plate_girder/ui_fields.py b/src/osdagbridge/core/bridge_types/plate_girder/ui_fields.py index a6f0bb9c..02989082 100644 --- a/src/osdagbridge/core/bridge_types/plate_girder/ui_fields.py +++ b/src/osdagbridge/core/bridge_types/plate_girder/ui_fields.py @@ -1,5 +1,16 @@ from osdagbridge.core.utils.common import * +""" +( + key, + display_name, + ui_type, + values, + is_visible, + validator, + ui_config_dict +) +""" class FrontendData: """Backend for Highway Bridge Design""" @@ -9,54 +20,225 @@ def __init__(self): self.design_button_status = False def input_values(self): - """Return list of input fields for the UI""" + """Return structured list of input definitions for the UI""" options_list = [] - t1 = (KEY_MODULE, KEY_DISP_FINPLATE, TYPE_MODULE, None, True, 'No Validator') - options_list.append(t1) + options_list.append( + (KEY_MODULE, KEY_DISP_FINPLATE, TYPE_MODULE, None, True, 'No Validator', {}) + ) - # Type of Structure section - t2 = (None, DISP_TITLE_STRUCTURE, TYPE_TITLE, None, True, 'No Validator') - options_list.append(t2) + # Type of Structure + options_list.append( + ( + "section_structure", + DISP_TITLE_STRUCTURE, + TYPE_TITLE, + None, + True, + 'No Validator', + { + "container": "main", + "post_note": { + "text": "*Other structures not included", + "attr": "structure_note", + }, + }, + ) + ) + options_list.append( + ( + KEY_STRUCTURE_TYPE, + KEY_DISP_STRUCTURE_TYPE, + TYPE_COMBOBOX, + VALUES_STRUCTURE_TYPE, + True, + 'No Validator', + {}, + ) + ) - t3 = (KEY_STRUCTURE_TYPE, KEY_DISP_STRUCTURE_TYPE, TYPE_COMBOBOX, VALUES_STRUCTURE_TYPE, True, 'No Validator') - options_list.append(t3) + # Project Location (custom content handled in dock) + options_list.append( + ( + "section_project_location", + DISP_TITLE_PROJECT, + TYPE_TITLE, + None, + True, + 'No Validator', + { + "container": "main", + "custom_content": "project_location", + "show_group_title": False, + "header_label": "Project Location*", + "button_rows": [ + { + "type": "project_location", + "buttons": [ + { + "text": "Add Here", + "action": "show_project_location_dialog", + } + ], + } + ], + }, + ) + ) - # Project Location section - t4 = (None, DISP_TITLE_PROJECT, TYPE_TITLE, None, True, 'No Validator') - options_list.append(t4) + # Geometric Details + options_list.append( + ( + "section_geometric", + DISP_TITLE_GEOMETRIC, + TYPE_TITLE, + None, + True, + 'No Validator', + { + "container": "superstructure", + "post_rows": [ + { + "type": "additional_geometry", + "label": "Additional Geometry", + "buttons": [ + { + "text": "Modify Here", + "action": "show_additional_inputs", + } + ], + } + ], + }, + ) + ) + options_list.append( + ( + KEY_SPAN, + KEY_DISP_SPAN, + TYPE_TEXTBOX, + None, + True, + 'Double Validator', + {"label": "Span*"}, + ) + ) + options_list.append( + ( + KEY_CARRIAGEWAY_WIDTH, + KEY_DISP_CARRIAGEWAY_WIDTH, + TYPE_TEXTBOX, + None, + True, + 'Double Validator', + {"label": "Carriageway Width*"}, + ) + ) + options_list.append( + ( + KEY_INCLUDE_MEDIAN, + "Include Median", + TYPE_COMBOBOX, + VALUES_YES_NO, + True, + 'No Validator', + {"label": "Include Median", "default": "No", "add_stretch": True}, + ) + ) + options_list.append( + ( + KEY_FOOTPATH, + KEY_DISP_FOOTPATH, + TYPE_COMBOBOX, + VALUES_FOOTPATH, + True, + 'No Validator', + {"label": "Footpath", "default": "None"}, + ) + ) + options_list.append( + ( + KEY_SKEW_ANGLE, + KEY_DISP_SKEW_ANGLE, + TYPE_TEXTBOX, + None, + True, + 'Double Validator', + {"label": "Skew Angle"}, + ) + ) - t5 = (KEY_PROJECT_LOCATION, KEY_DISP_PROJECT_LOCATION, TYPE_COMBOBOX, VALUES_PROJECT_LOCATION, True, 'No Validator') - options_list.append(t5) - - # Geometric Details section - t6 = (None, DISP_TITLE_GEOMETRIC, TYPE_TITLE, None, True, 'No Validator') - options_list.append(t6) - - t7 = (KEY_SPAN, KEY_DISP_SPAN, TYPE_TEXTBOX, None, True, 'Double Validator') - options_list.append(t7) - - t8 = (KEY_CARRIAGEWAY_WIDTH, KEY_DISP_CARRIAGEWAY_WIDTH, TYPE_TEXTBOX, None, True, 'Double Validator') - options_list.append(t8) - - t9 = (KEY_FOOTPATH, KEY_DISP_FOOTPATH, TYPE_COMBOBOX, VALUES_FOOTPATH, True, 'No Validator') - options_list.append(t9) - - t10 = (KEY_SKEW_ANGLE, KEY_DISP_SKEW_ANGLE, TYPE_TEXTBOX, None, True, 'Double Validator') - options_list.append(t10) - - # Material Inputs section - t11 = (None, DISP_TITLE_MATERIAL, TYPE_TITLE, None, True, 'No Validator') - options_list.append(t11) - - t12 = (KEY_GIRDER, KEY_DISP_GIRDER, TYPE_COMBOBOX, connectdb("Material"), True, 'No Validator') - options_list.append(t12) - - t13 = (KEY_CROSS_BRACING, KEY_DISP_CROSS_BRACING, TYPE_COMBOBOX, connectdb("Material"), True, 'No Validator') - options_list.append(t13) - - t14 = (KEY_DECK, KEY_DISP_DECK, TYPE_COMBOBOX, connectdb("Material"), True, 'No Validator') - options_list.append(t14) + # Material Inputs + options_list.append( + ( + "section_material", + DISP_TITLE_MATERIAL, + TYPE_TITLE, + None, + True, + 'No Validator', + { + "container": "superstructure", + "post_rows": [ + { + "type": "material_properties", + "label": "Properties", + "buttons": [ + { + "text": "Modify Here", + "action": "show_material_properties_dialog", + } + ], + } + ], + }, + ) + ) + material_values = connectdb("Material") + options_list.append( + ( + KEY_GIRDER, + KEY_DISP_GIRDER, + TYPE_COMBOBOX, + material_values, + True, + 'No Validator', + {"label": "Girder"}, + ) + ) + options_list.append( + ( + KEY_CROSS_BRACING, + KEY_DISP_CROSS_BRACING, + TYPE_COMBOBOX, + material_values, + True, + 'No Validator', + {"label": "Cross Bracing"}, + ) + ) + options_list.append( + ( + KEY_END_DIAPHRAGM, + KEY_DISP_END_DIAPHRAGM, + TYPE_COMBOBOX, + material_values, + True, + 'No Validator', + {"label": "End Diaphragm"}, + ) + ) + options_list.append( + ( + KEY_DECK_CONCRETE_GRADE_BASIC, + KEY_DISP_DECK_CONCRETE_GRADE, + TYPE_COMBOBOX, + VALUES_DECK_CONCRETE_GRADE, + True, + 'No Validator', + {"label": "Deck", "default": "M 25"}, + ) + ) return options_list diff --git a/src/osdagbridge/desktop/resources/__init__.py b/src/osdagbridge/desktop/resources/__init__.py new file mode 100644 index 00000000..068deaab --- /dev/null +++ b/src/osdagbridge/desktop/resources/__init__.py @@ -0,0 +1,3 @@ +"""Resource package exposing compiled Qt assets.""" + +from . import resources_rc # noqa: F401 re-export for legacy imports diff --git a/src/osdagbridge/desktop/ui/dialogs/__pycache__/resources/resources.qrc b/src/osdagbridge/desktop/ui/dialogs/__pycache__/resources/resources.qrc new file mode 100644 index 00000000..a876dcf2 --- /dev/null +++ b/src/osdagbridge/desktop/ui/dialogs/__pycache__/resources/resources.qrc @@ -0,0 +1,36 @@ + + + + + + + + + vectors/arrow_down_light.svg + vectors/arrow_down_dark.svg + vectors/arrow_up_light.svg + vectors/arrow_up_dark.svg + + themes/lightstyle.qss + + vectors/design.svg + vectors/save.svg + vectors/checked.svg + vectors/design_report.svg + vectors/lock_open.svg + vectors/lock_close.svg + vectors/Osdag_logo.svg + + vectors/inputs_label_light.svg + vectors/input_dock_active_light.svg + vectors/input_dock_inactive_light.svg + + vectors/logs_dock_active_light.svg + vectors/logs_dock_inactive_light.svg + + vectors/output_dock_active_light.svg + vectors/output_dock_inactive_light.svg + vectors/outputs_label_light.svg + + + diff --git a/src/osdagbridge/desktop/ui/dialogs/__pycache__/resources/resources_rc.py b/src/osdagbridge/desktop/ui/dialogs/__pycache__/resources/resources_rc.py new file mode 100644 index 00000000..79a25e66 --- /dev/null +++ b/src/osdagbridge/desktop/ui/dialogs/__pycache__/resources/resources_rc.py @@ -0,0 +1,1471 @@ +# Resource object code (Python 3) +# Created by: object code +# Created by: The Resource Compiler for Qt version 6.9.2 +# WARNING! All changes made in this file will be lost! + +from PySide6 import QtCore + +qt_resource_data = b"\ +\x00\x00\x04\xe7\ +\x00\ +\x00\x15\xe8x\xda\xd5X]O\xdb<\x14\xbeG\xe2?\ +X\xea\xc5>\xb4\xae-\x85\x8e\x05\xbd\x17\xfd\x82!\x15\ +\x18\xb4\xdb\xc4n\x90\x93\xb8\xad\x85\x1bg\x8eC\xe1\x9d\ +\xf8\xef\xef\xb1\x9b\xef&n`\xef\xcdb\x0du\xb6s\ +\xce\xe3\xc7\x8f\xcf9N\xeb=\xfa\xe7E\xcf\xfe\x1eB\ +\xe8j:\xea\x9f\xa1\xb3o\xe7h:\xbb\x9d\x8c\xa7_\ +\xc6\xe3\x19j\xa2\xab\x9b\xb3\xfe\xe5\xf9\xcf\xf1\x08\x0dn\ +\xd1\xf4\xebxx~z><\x9f\xdd\xeaw^\xe6\x06\ +\xbdo\xed\xef\xed\xef\xb5^\x05\xaf\xf3\x11\x9dM\xae\x06\ +\xfdI\x04\x0f\xbd\x9d\x10\x1cH4\xf5\x89C\xe7\xd4y\ +\xf7Z@\xd7\x17\x98z?\xa8\xe7\xf25\xfa\xad\x8d \ +\x1b;\xf7\x0b\xc1C\xcfm:\x9cqa\xa1\xc6\xfcP\ +\xb5\x93h\x9c\x0b\x97@o\xc7\x7fD\x01g\xd4E\x8d\ +\xcfm<\xeft\xa3\xf1\x15\x16\x0b\xeaY\xa8\xed?F\ +=>v]\xea-\xe2\xaeg\xc5\xc3\xf5\x8cs6\xa3\ +\xbe\xc1\xeb\xa9~\x22\x1bqg[?&(\xfd\xd3\x04\ +J\xe2\xf8\x00&\x1c$x\xe6\xdc\x93\xcd\x80\xfeK\xe0\ +\xcd\xb4wc\xac)\xb0K\xc3 \x0b\xff\x97/\xb8O\ +\x84|jbF\x17\xde\x8ax\xd2B}\xf5\xf3\xfb\x10\ +~\x13\xb1Y\xd3\xf5\xd4gT\xc2\x7f-k\x89=\x97\ +\x11\xc3\xd2Fm\xd5b.@\x13g\x8c\xdb\x98\xa1\xe1\ +\x928\xf76\x7fDS\xf9\x04\x06\xf4\x0e\xe9\xbe\x01\xf4\ +\xfd\xdeF\x9f\x82\x8c-w\xbb\xf1\xe2\x03\x1f;z\xf1\ +\xbd\x98\xf5\xc4\x94e\xc1\x8eS\x07K.b\xabk\xea\ +\xca%X\xec%\x16\x97\x84.\x962\xd7U\xc2wW\ +?\xe5\x14v\xd3\x17K4\xa5\x9fj`\xd6\x92?\x90\ +\x04^\x89\xe7#\xfd\x18\x0c8\xaa\x8f\xb8&]\xc7\x18\ +\xea,\x8e\xae\xf0\x028\x0f\x05{k\xb5\x1e\x88\x03.\ +\x82V\xe4\xe3c\xf0\xb0x\x97H\xfb\x82x\xe1\x00\x8b\ +\x86$+\x9faI\xee|x\xf3\x0et\x13\xde\xd9X\ +\x98\x14\x7f\xa8\x9aI\xf1\xdbGi\x877 \x04\x06b\ +\x9f\xc9\xebG\xb0\xc8\x8czR,\x16\x92\x02{\xa0\x1d\ +\x01\xd26\x9d\x8c\xba\xae\xad\x800 \xabl\x1f\xb2g\ +\xbc\xb69_\x90 \xa8\xb06>V\xad\x965\xa4\xc7\ +\xebF\x9f\x12m$G\xb8\x84\xa1CC\xe8\xab\x85\xab\ +z\xd7L\xe2\xc8F\x86\xce+\xfc\x19\xb6*\xa1e\xdc\ +;m\x17h)\x9c\xf7\xfa>\x03\x022\xcb\x06\xa1$\ +\xe4\x94\x0a\xb3\x01\x9e\xa1\xe5\xd2L\x93\x91\xb9\xb42\xb1\ +=\xea\x16\x1bC[\xfd\x92\xfb\xd9\xd0\x1e\xf5\xda\x5cJ\ +\xbez\xf9&i/M,D\x9a=\xa38z\xbc\x15\ +F\x8f\xd3\xdc\x07\xf1~\x04)\x05r\xae\x87\x94I\x14\ +\xa4\xd1\xbe\x5c\x95\x9bXU3\xebeephN\xc2\ +\x06\xad\x1d\xab\x08\xd13\xe4\x97\x18\x86\xc7=RH\xfd\ +\x9dR\x17\xe6H\x90[B&@\x97K\xed \xc7\xe6\ ++*\xaa.TT\xe3\xcb\xf1\x0d\x94T\x83o\xb3\xd9\ +\xd5eRY\x0dp@\xd0\x84<\x10\xf6\xfa\xaa\xeak\ +\x18,\x07!\x88\xca\xab>J\xeb%\x90\x92_\xaf\xcd\ +`Vv\x17\xd7\x91xl\xce\xdc\x13T\xca\xc4\x91!\ +;g\xed\xe5C\x7f\xaa\x0bI\x1e\xe5\xa6\xb0\xb1\x90\x93\ +)gr\xab($\xe3\xed\xb0\x90\xdb\xbd]\x02-\x10\ +\xb0\xe5\xac\x10\xe2K\xc81\xb3YM\xc4\xc6\xd5\x0f\xea\ +.\x88l\x0c\xc3\x00N\xfd\x8cJF\x06*-\xa3\xdd\ +\xf5\xaf\x0a\x0d\x13l\x13\xd6\xd0\xaf\xe9\x9f\x05\x98\xc6|\ +m\xce\xb5\xa9\xf5\x09_\xf0\x9cqs\x82.\xadg\xb6\ +\xc3@Zxo\x88n\x5cP\x8f\xae`\xc6.\xa9V\ +{,-\xc7\xdb\xbbk\xea,\xb8\x9ey\xfa\x0e\xd4;\ +\xa59\xef\xa8V\xc3Pee\x91\x98\xc2=\xd5JM\ +\xe1\xc7\x1b\x02r\x12\x7f\x1b\x97\x05\xdc\x7f\xc2f\xd1\xd4\ +\x9f\xf09d<\xf8\xcb\xa8\xcc@\xde\xc9\x22\xf9t\xe8\ +t\x9d\xaa\xd3k4\xbd\x9bU\xa7\xdd\xfd|`G\xf1\ +$\x8au\x03]\xe1L\xa8G\xea\x04\xf1g\x95U+\ +\x12\x1c\x84\xa6\xa09\xe2\xce\xbd!\x09\xea\x1c\x18yf\ +0\xff\xce\x85\xf9\xe8\xba\x22\xa2\xa5%\xf7\x81j\xb5\xae\ +?\xdd\xfc\x96\xe5\xf2d\xd5%\xff\xb9\x14\xd4\x0c\xf2\xdf\ +\xd8\xa5\xd2\x84\xebX\xb5\x9aW\x01\xedw\x8eW\x94=\ +Y\xe8\xcd\x90\x87\x82\x82\x14.\xc9\xfa\xcd\x07\xb4\xe2\x1e\ +W\xd7rb\xfc\x10Q\xa7\xdc/_\xcb\xd4\x11\x9c1\ +Hf\x16\xc8O\xc25\x98\x95_\x93\xda\xaa\x9d \xa8\ +\x9c&\x8a7\xb4\x10\xe4\x09\xc0\x08$\x97\x04\x05\xda\x88\ +\xaar\xe1|\x81U\xb5\x99\xa5\x95m\xe6CO\xf2\xaf\ +[uhL[\x90\xc2\x8e\xbe\x9c\x98\xe1\xf7\xdb\xaai\ +\xf8\x17\x04\xac\xaf\xaa\xf0G\x9fa\xe2\x05\xac\xa0\xd2\x8f\ +\xab\xf1n\xfb\xff\x05Zu\xe2\x01\xee\xa7\xb6j\x1a\xee\ +\x08\x8b{\x98\xa5\xe1B`\xdb\xbc\x13\xe3\xce\xa0\xdd\xe9\ +\x1d\x14\xd2dp\x98\x13\xff\x1fr\xc3Ah\xe7\x87\x8b\ +\x17,\xb5z\x05\xe8\x0buI\xa0\xbd\x87~K\xdfG\ +\xf4e&\xa8\x8fB\xdd\x8b\x0c(r\xc3e\xf4\xe8\xdb\ +C\x1e\x0a\x04t\x8cl\x22\xd7\x84x1+\xf0\xb7\x00\ +\xed??\x04\x81\x7f\ +\x00\x00\x00\xb2\ +<\ +svg xmlns=\x22http:\ +//www.w3.org/200\ +0/svg\x22 height=\x222\ +4px\x22 viewBox=\x220 \ +-960 960 960\x22 wi\ +dth=\x2224px\x22 fill=\ +\x22#90af13\x22>\ +\x00\x00\x02-\ +<\ +svg width=\x2240\x22 h\ +eight=\x2240\x22 viewB\ +ox=\x220 0 40 40\x22 f\ +ill=\x22none\x22 xmlns\ +=\x22http://www.w3.\ +org/2000/svg\x22>\x0d\x0a\ +\x0d\x0a\x0d\x0a\ +\x00\x00\x0e;\ +<\ +svg width=\x22149\x22 \ +height=\x22141\x22 vie\ +wBox=\x220 0 149 14\ +1\x22 fill=\x22none\x22 x\ +mlns=\x22http://www\ +.w3.org/2000/svg\ +\x22>\x0d\x0a\x0d\x0a<\ +path d=\x22M130.212\ + 67.3074L115.887\ + 51.8725L130.121\ + 36.1514L130.212\ + 67.3074Z\x22 fill=\ +\x22black\x22/>\x0d\x0a\x0d\x0a\x0d\x0a\x0d\x0a\x0d\x0a\x0d\x0a\x0d\x0a\x0d\x0a\x0d\x0a\ +\x0d\x0a\x0d\x0a\ +\x0d\x0a\x0d\x0a\ +\x00\x00\x02\xfe\ +<\ +svg xmlns=\x22http:\ +//www.w3.org/200\ +0/svg\x22 height=\x224\ +0px\x22 viewBox=\x220 \ +-960 960 960\x22 wi\ +dth=\x2240px\x22 fill=\ +\x22#000000\x22>\ +\x00\x00\x02\x07\ +<\ +svg width=\x2240\x22 h\ +eight=\x2240\x22 viewB\ +ox=\x220 0 40 40\x22 f\ +ill=\x22none\x22 xmlns\ +=\x22http://www.w3.\ +org/2000/svg\x22>\x0d\x0a\ +\x0d\x0a\x0d\x0a\ +\x00\x00\x021\ +<\ +svg xmlns=\x22http:\ +//www.w3.org/200\ +0/svg\x22 height=\x224\ +0px\x22 viewBox=\x220 \ +-960 960 960\x22 wi\ +dth=\x2240px\x22 fill=\ +\x22#FFFFFF\x22>\ +\ +\x00\x00\x01\xa4\ +<\ +svg xmlns=\x22http:\ +//www.w3.org/200\ +0/svg\x22 height=\x224\ +0px\x22 viewBox=\x220 \ +-960 960 960\x22 wi\ +dth=\x2240px\x22 fill=\ +\x22#000000\x22>\ +\x00\x00\x02\xd3\ +<\ +svg xmlns=\x22http:\ +//www.w3.org/200\ +0/svg\x22 height=\x224\ +0px\x22 viewBox=\x220 \ +-960 960 960\x22 wi\ +dth=\x2240px\x22 fill=\ +\x22#FFFFFF\x22>\ +\x00\x00\x05O\ +<\ +svg width=\x2240\x22 h\ +eight=\x2240\x22 viewB\ +ox=\x220 0 40 40\x22 f\ +ill=\x22none\x22 xmlns\ +=\x22http://www.w3.\ +org/2000/svg\x22>\x0d\x0a\ +\x0d\x0a\x0d\x0a\ +\x00\x00\x08+\ +<\ +svg width=\x2236\x22 h\ +eight=\x22922\x22 view\ +Box=\x220 0 36 922\x22\ + fill=\x22none\x22 xml\ +ns=\x22http://www.w\ +3.org/2000/svg\x22>\ +\x0d\x0a\x0d\x0a\ +\x0d\x0a\x0d\x0a\ +\x00\x00\x02\xde\ +<\ +svg xmlns=\x22http:\ +//www.w3.org/200\ +0/svg\x22 height=\x224\ +0px\x22 viewBox=\x220 \ +-960 960 960\x22 wi\ +dth=\x2240px\x22 fill=\ +\x22#000000\x22>\ +\x00\x00\x021\ +<\ +svg xmlns=\x22http:\ +//www.w3.org/200\ +0/svg\x22 height=\x224\ +0px\x22 viewBox=\x220 \ +-960 960 960\x22 wi\ +dth=\x2240px\x22 fill=\ +\x22#000000\x22>\ +\ +\x00\x00\x0aV\ +<\ +svg width=\x2236\x22 h\ +eight=\x22918\x22 view\ +Box=\x220 0 36 918\x22\ + fill=\x22none\x22 xml\ +ns=\x22http://www.w\ +3.org/2000/svg\x22>\ +\x0d\x0a\x0d\x0a\ +\x0d\x0a\x0d\x0a\ +\x00\x00\x05O\ +<\ +svg width=\x2240\x22 h\ +eight=\x2240\x22 viewB\ +ox=\x220 0 40 40\x22 f\ +ill=\x22none\x22 xmlns\ +=\x22http://www.w3.\ +org/2000/svg\x22>\x0d\x0a\ +\x0d\x0a\x0d\x0a\ +\x00\x00\x02\xbb\ +<\ +svg xmlns=\x22http:\ +//www.w3.org/200\ +0/svg\x22 height=\x224\ +0px\x22 viewBox=\x220 \ +-960 960 960\x22 wi\ +dth=\x2240px\x22 fill=\ +\x22#FFFFFF\x22>\ +\x00\x00\x02/\ +<\ +svg width=\x2240\x22 h\ +eight=\x2240\x22 viewB\ +ox=\x220 0 40 40\x22 f\ +ill=\x22none\x22 xmlns\ +=\x22http://www.w3.\ +org/2000/svg\x22>\x0d\x0a\ +\x0d\x0a\x0d\x0a\ +\x00\x00\x01\x9d\ +<\ +svg xmlns=\x22http:\ +//www.w3.org/200\ +0/svg\x22 height=\x224\ +0px\x22 viewBox=\x220 \ +-960 960 960\x22 wi\ +dth=\x2240px\x22 fill=\ +\x22#000000\x22>\ +\x00\x00\x01\xa2\ +<\ +svg xmlns=\x22http:\ +//www.w3.org/200\ +0/svg\x22 height=\x224\ +0px\x22 viewBox=\x220 \ +-960 960 960\x22 wi\ +dth=\x2240px\x22 fill=\ +\x22#000000\x22>\ +\x00\x00\x025\ +<\ +svg xmlns=\x22http:\ +//www.w3.org/200\ +0/svg\x22 height=\x224\ +0px\x22 viewBox=\x220 \ +-960 960 960\x22 wi\ +dth=\x2240px\x22 fill=\ +\x22#FFFFFF\x22>\ +" + +qt_resource_name = b"\ +\x00\x07\ +\x0c\xba\xb6s\ +\x00v\ +\x00e\x00c\x00t\x00o\x00r\x00s\ +\x00\x06\ +\x07\xae\xc3\xc3\ +\x00t\ +\x00h\x00e\x00m\x00e\x00s\ +\x00\x0e\ +\x03\x9b1c\ +\x00l\ +\x00i\x00g\x00h\x00t\x00s\x00t\x00y\x00l\x00e\x00.\x00q\x00s\x00s\ +\x00\x0b\ +\x01d\x8d\x87\ +\x00c\ +\x00h\x00e\x00c\x00k\x00e\x00d\x00.\x00s\x00v\x00g\ +\x00\x1b\ +\x0aa\xaa'\ +\x00i\ +\x00n\x00p\x00u\x00t\x00_\x00d\x00o\x00c\x00k\x00_\x00a\x00c\x00t\x00i\x00v\x00e\ +\x00_\x00l\x00i\x00g\x00h\x00t\x00.\x00s\x00v\x00g\ +\x00\x0e\ +\x06\xb1\x9b\x07\ +\x00O\ +\x00s\x00d\x00a\x00g\x00_\x00l\x00o\x00g\x00o\x00.\x00s\x00v\x00g\ +\x00\x0e\ +\x0d\xd6\xeag\ +\x00l\ +\x00o\x00c\x00k\x00_\x00c\x00l\x00o\x00s\x00e\x00.\x00s\x00v\x00g\ +\x00\x1a\ +\x0b}\x5cG\ +\x00l\ +\x00o\x00g\x00s\x00_\x00d\x00o\x00c\x00k\x00_\x00a\x00c\x00t\x00i\x00v\x00e\x00_\ +\x00l\x00i\x00g\x00h\x00t\x00.\x00s\x00v\x00g\ +\x00\x13\ +\x0cPg\xa7\ +\x00a\ +\x00r\x00r\x00o\x00w\x00_\x00d\x00o\x00w\x00n\x00_\x00d\x00a\x00r\x00k\x00.\x00s\ +\x00v\x00g\ +\x00\x1e\ +\x08a\xb9\xc7\ +\x00o\ +\x00u\x00t\x00p\x00u\x00t\x00_\x00d\x00o\x00c\x00k\x00_\x00i\x00n\x00a\x00c\x00t\ +\x00i\x00v\x00e\x00_\x00l\x00i\x00g\x00h\x00t\x00.\x00s\x00v\x00g\ +\x00\x0a\ +\x0f\xec\x03\xe7\ +\x00d\ +\x00e\x00s\x00i\x00g\x00n\x00.\x00s\x00v\x00g\ +\x00\x12\ +\x0aDDG\ +\x00a\ +\x00r\x00r\x00o\x00w\x00_\x00u\x00p\x00_\x00l\x00i\x00g\x00h\x00t\x00.\x00s\x00v\ +\x00g\ +\x00\x16\ +\x0e\x16='\ +\x00i\ +\x00n\x00p\x00u\x00t\x00s\x00_\x00l\x00a\x00b\x00e\x00l\x00_\x00l\x00i\x00g\x00h\ +\x00t\x00.\x00s\x00v\x00g\ +\x00\x0d\ +\x005w\xc7\ +\x00l\ +\x00o\x00c\x00k\x00_\x00o\x00p\x00e\x00n\x00.\x00s\x00v\x00g\ +\x00\x14\ +\x01\xd3}\xa7\ +\x00a\ +\x00r\x00r\x00o\x00w\x00_\x00d\x00o\x00w\x00n\x00_\x00l\x00i\x00g\x00h\x00t\x00.\ +\x00s\x00v\x00g\ +\x00\x17\ +\x0d\xa0\xcd'\ +\x00o\ +\x00u\x00t\x00p\x00u\x00t\x00s\x00_\x00l\x00a\x00b\x00e\x00l\x00_\x00l\x00i\x00g\ +\x00h\x00t\x00.\x00s\x00v\x00g\ +\x00\x11\ +\x03\xf9t'\ +\x00a\ +\x00r\x00r\x00o\x00w\x00_\x00u\x00p\x00_\x00d\x00a\x00r\x00k\x00.\x00s\x00v\x00g\ +\ +\x00\x11\ +\x08\xb1e\xc7\ +\x00d\ +\x00e\x00s\x00i\x00g\x00n\x00_\x00r\x00e\x00p\x00o\x00r\x00t\x00.\x00s\x00v\x00g\ +\ +\x00\x1c\ +\x0ap\xe4'\ +\x00o\ +\x00u\x00t\x00p\x00u\x00t\x00_\x00d\x00o\x00c\x00k\x00_\x00a\x00c\x00t\x00i\x00v\ +\x00e\x00_\x00l\x00i\x00g\x00h\x00t\x00.\x00s\x00v\x00g\ +\x00\x1c\ +\x01\xd8[\xc7\ +\x00l\ +\x00o\x00g\x00s\x00_\x00d\x00o\x00c\x00k\x00_\x00i\x00n\x00a\x00c\x00t\x00i\x00v\ +\x00e\x00_\x00l\x00i\x00g\x00h\x00t\x00.\x00s\x00v\x00g\ +\x00\x1d\ +\x0e\xaf\xb9\xe7\ +\x00i\ +\x00n\x00p\x00u\x00t\x00_\x00d\x00o\x00c\x00k\x00_\x00i\x00n\x00a\x00c\x00t\x00i\ +\x00v\x00e\x00_\x00l\x00i\x00g\x00h\x00t\x00.\x00s\x00v\x00g\ +\x00\x08\ +\x08\xc8U\xe7\ +\x00s\ +\x00a\x00v\x00e\x00.\x00s\x00v\x00g\ +" + +qt_resource_struct = b"\ +\x00\x00\x00\x00\x00\x02\x00\x00\x00\x02\x00\x00\x00\x01\ +\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x14\x00\x02\x00\x00\x00\x01\x00\x00\x00\x16\ +\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x02\x00\x00\x00\x13\x00\x00\x00\x03\ +\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x02\x02\x00\x00\x00\x00\x00\x01\x00\x00/T\ +\x00\x00\x01\x9b\x13E\x17;\ +\x00\x00\x00H\x00\x00\x00\x00\x00\x01\x00\x00\x04\xeb\ +\x00\x00\x01\x9b\x13E\x179\ +\x00\x00\x02\x22\x00\x00\x00\x00\x00\x01\x00\x0026\ +\x00\x00\x01\x9b\x13E\x179\ +\x00\x00\x03\x12\x00\x00\x00\x00\x00\x01\x00\x00I\x0a\ +\x00\x00\x01\x9a\xfc\x9f}A\ +\x00\x00\x02\x84\x00\x00\x00\x00\x00\x01\x00\x00>\xc5\ +\x00\x00\x01\x9b\x13E\x179\ +\x00\x00\x00\xa0\x00\x00\x00\x00\x00\x01\x00\x00\x07\xd2\ +\x00\x00\x01\x9b\x13\xd0_\xa8\ +\x00\x00\x01J\x00\x00\x00\x00\x00\x01\x00\x00\x1dS\ +\x00\x00\x01\x9a\xfc\x9f}M\ +\x00\x00\x02\xac\x00\x00\x00\x00\x00\x01\x00\x00D\x18\ +\x00\x00\x01\x9b\x13E\x179\ +\x00\x00\x03\x90\x00\x00\x00\x00\x00\x01\x00\x00LQ\ +\x00\x00\x01\x9b\x13E\x17;\ +\x00\x00\x01\xa6\x00\x00\x00\x00\x00\x01\x00\x00!\xd2\ +\x00\x00\x01\x9b\x13E\x179\ +\x00\x00\x00d\x00\x00\x00\x00\x00\x01\x00\x00\x05\xa1\ +\x00\x00\x01\x9a\xfc\x9f};\ +\x00\x00\x02\xd4\x00\x00\x00\x00\x00\x01\x00\x00F\xd7\ +\x00\x00\x01\x9a\xfc\x9f}K\ +\x00\x00\x00\xe4\x00\x00\x00\x00\x00\x01\x00\x00\x19\x13\ +\x00\x00\x01\x9a\xfc\x9f}A\ +\x00\x00\x01\x1e\x00\x00\x00\x00\x00\x01\x00\x00\x1b\x1e\ +\x00\x00\x01\x9b\x13E\x179\ +\x00\x00\x02P\x00\x00\x00\x00\x00\x01\x00\x004k\ +\x00\x00\x01\x9b\x07\x002e\ +\x00\x00\x00\xc2\x00\x00\x00\x00\x00\x01\x00\x00\x16\x11\ +\x00\x00\x01\x9b\x13E\x179\ +\x00\x00\x01\xd0\x00\x00\x00\x00\x00\x01\x00\x00'%\ +\x00\x00\x01\x9b\x07\x002a\ +\x00\x00\x03P\x00\x00\x00\x00\x00\x01\x00\x00J\xab\ +\x00\x00\x01\x9a\xfc\x9f}<\ +\x00\x00\x01\x8c\x00\x00\x00\x00\x00\x01\x00\x00\x1e\xfb\ +\x00\x00\x01\x9b\x13E\x179\ +\x00\x00\x00&\x00\x01\x00\x00\x00\x01\x00\x00\x00\x00\ +\x00\x00\x01\x9b\x13\xd50\xdc\ +" + +def qInitResources(): + QtCore.qRegisterResourceData(0x03, qt_resource_struct, qt_resource_name, qt_resource_data) + +def qCleanupResources(): + QtCore.qUnregisterResourceData(0x03, qt_resource_struct, qt_resource_name, qt_resource_data) + +qInitResources() diff --git a/src/osdagbridge/desktop/ui/dialogs/__pycache__/resources/themes/darkstyle.qss b/src/osdagbridge/desktop/ui/dialogs/__pycache__/resources/themes/darkstyle.qss new file mode 100644 index 00000000..f9c6f9ac --- /dev/null +++ b/src/osdagbridge/desktop/ui/dialogs/__pycache__/resources/themes/darkstyle.qss @@ -0,0 +1,32 @@ +/* ============================================== + OSDAG GUI STYLESHEET - ORGANIZED BY SPECIFICITY + ============================================== */ + +/* ============================================== + 1. GLOBAL STYLES (Least Specific) + ============================================== */ +* { + font-family: "Ubuntu Sans"; +} + +QMainWindow { + background-color: #282828; + border: 1px solid #6B7D20; + margin: 0px; + padding: 0px; +} + +QTabWidget { + background-color: #333333; + border: 0px; +} + +QToolTip { + background-color: #2B2B2B; + color: #D0D0D0; + border: 1px solid #6B7D20; + padding: 2px 2px; + font-size: 12px; + border-radius: 0px; + qproperty-alignment: AlignVCenter; +} \ No newline at end of file diff --git a/src/osdagbridge/desktop/ui/dialogs/__pycache__/resources/themes/lightstyle.qss b/src/osdagbridge/desktop/ui/dialogs/__pycache__/resources/themes/lightstyle.qss new file mode 100644 index 00000000..452eeeb6 --- /dev/null +++ b/src/osdagbridge/desktop/ui/dialogs/__pycache__/resources/themes/lightstyle.qss @@ -0,0 +1,243 @@ +/* ============================================== + OSDAG GUI STYLESHEET - ORGANIZED BY SPECIFICITY + ============================================== */ + +/* ============================================== + 1. GLOBAL STYLES (Least Specific) + ============================================== */ +QMainWindow { + background-color: #f4f4f4; + border: 1px solid #90af13; + margin: 0px; + padding: 0px; +} + +QToolTip { + background-color: #FFFFFF; + color: #000000; + border: 1px solid #90AF13; + padding: 2px 2px; + font-size: 12px; + border-radius: 0px; + qproperty-alignment: AlignVCenter; +} +QSplitter::handle { + background-color: #D0D0D0; +} + +/* Global Checkbox Style */ +QCheckBox { + font-size: 10px; + color: #333; + spacing: 6px; +} +QCheckBox::indicator { + width: 16px; + height: 16px; + border: 1px solid #333333; + border-radius: 3px; + background-color: #ffffff; +} +QCheckBox::indicator:hover { + border: 1px solid #555555; +} +QCheckBox::indicator:checked { + background-color: #ffffff; + border: 1px solid #333333; + image: url(:/vectors/checked.svg); +} + +QMenuBar#template_page_menu_bar { + background-color: #F4F4F4; + color: #000000; + padding: 0px; +} +QMenuBar#template_page_menu_bar::item { + padding: 5px 10px; + background: transparent; + border-radius: 0px; +} +QMenuBar#template_page_menu_bar::item:selected { + background: #FFFFFF; +} +QMenuBar#template_page_menu_bar::item:pressed { + background: #E8E8E8; +} +QMenuBar#template_page_menu_bar QMenu { + background-color: #FFFFFF; + border: 1px solid #D0D0D0; + border-radius: 4px; + padding: 0px; +} +QMenuBar#template_page_menu_bar QMenu::item { + padding: 5px; + color: #000000; + font-size: 11px; +} +QMenuBar#template_page_menu_bar QMenu::item:selected { + background-color: #E6F0FF; + border-radius: 3px; +} +QMenuBar#template_page_menu_bar QMenu::separator { + height: 1px; + background: #F0F0F0; + margin-left: 2px; + margin-right: 2px; + margin-top: 0px; + margin-bottom: 0px; +} +QMenuBar#template_page_menu_bar QMenu::right-arrow { + width: 8px; + height: 8px; +} + +/* Dropdown menu style */ +QMenu { + background: #fff; + border: 1px solid #90AF13; + font-size: 14px; + padding: 0px; +} + +QMenu::item { + padding: 8px 16px; + color: #333; + border: none; + margin: 1px; +} + +QMenu::item:selected { + background: #90AF13; + color: #fff; + border-radius: 2px; +} + +/* ============================================== + 3. GENERAL BUTTON STYLES (Base Level) + ============================================== */ +QPushButton { + background-color: white; + color: black; + font-weight: bold; + border-radius: 5px; + border: 1px solid black; + padding: 5px 14px; + text-align: center; +} + +QPushButton:hover { + background-color: #90AF13; + border: 1px solid #90AF13; + color: white; +} + +QPushButton:pressed { + color: black; + background-color: white; + border: 1px solid black; +} + +QWidget#CustomTitleBar { + background-color: #f4f4f4; +} +QLabel#TitleLabel { + color: #000000; + padding: 0px; + background: transparent; +} +QLabel#LogoLabel { + background: transparent; + color: #ffffff; + font-size: 14px; +} + +QToolButton#MinimizeButton { + background-color: transparent; + color: #000000; + border: 0px; + border-radius: 0px; + font-size: 16px; + border-radius: 0px; +} + +QToolButton#MinimizeButton:hover { + background-color: #f1f1f1; +} + +QToolButton#MinimizeButton:pressed { + background-color: #a6a6a6; +} + +QToolButton#MaxRestoreButton { + background-color: transparent; + color: #000000; + border: 0px; + border-radius: 0px; + font-size: 16px; + border-radius: 0px; +} + +QToolButton#MaxRestoreButton:hover { + background-color: #f1f1f1; +} + +QToolButton#MaxRestoreButton:pressed { + background-color: #a6a6a6; +} + +QToolButton#CloseButton { + background-color: transparent; + color: #000000; + border: 0px; + border-radius: 0px; + font-size: 16px; + border-radius: 0px; +} + +QToolButton#CloseButton:hover { + background-color: #e74c3c; + color: #ffffff; +} + +QToolButton#CloseButton:pressed { + background-color: #c0392b; +} +QWidget#BottomLine { + background-color: #90AF13; +} +/*=======================Logs-Dock===========================*/ +QWidget#logs_dock QLabel { + background-color: #F2F2F2; + color: #000000; + padding: 3px; + font-weight: bold; + font-size: 12px; +} +QWidget#logs_dock QTextEdit { + background-color: #F8F8F8; + border: 1px solid #D0D0D0; + font-family: 'Courier New', monospace; + font-size: 12px; + padding: 5px; + color: #000000; +} +QWidget#logs_dock QScrollBar:vertical { + background: #E0E0E0; /* Light grey for the scrollbar track */ + width: 8px; + margin: 0px 0px 0px 3px; + border-radius: 2px; +} +QWidget#logs_dock QScrollBar::handle:vertical { + background: #A0A0A0; /* Medium grey for the scrollbar handle */ + min-height: 30px; + border-radius: 2px; +} +QWidget#logs_dock QScrollBar::handle:vertical:hover { + background: #707070; /* Darker grey on hover for the handle */ +} +QWidget#logs_dock QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical { + height: 0px; /* Hides the up/down arrows */ +} +QWidget#logs_dock QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { + background: none; /* Hides the area between handle and arrows */ +} diff --git a/src/osdagbridge/desktop/ui/dialogs/__pycache__/resources/vectors/Osdag_logo.svg b/src/osdagbridge/desktop/ui/dialogs/__pycache__/resources/vectors/Osdag_logo.svg new file mode 100644 index 00000000..1ee3a590 --- /dev/null +++ b/src/osdagbridge/desktop/ui/dialogs/__pycache__/resources/vectors/Osdag_logo.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/src/osdagbridge/desktop/ui/dialogs/__pycache__/resources/vectors/arrow_down_dark.svg b/src/osdagbridge/desktop/ui/dialogs/__pycache__/resources/vectors/arrow_down_dark.svg new file mode 100644 index 00000000..2c10661b --- /dev/null +++ b/src/osdagbridge/desktop/ui/dialogs/__pycache__/resources/vectors/arrow_down_dark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/osdagbridge/desktop/ui/dialogs/__pycache__/resources/vectors/arrow_down_light.svg b/src/osdagbridge/desktop/ui/dialogs/__pycache__/resources/vectors/arrow_down_light.svg new file mode 100644 index 00000000..52e93659 --- /dev/null +++ b/src/osdagbridge/desktop/ui/dialogs/__pycache__/resources/vectors/arrow_down_light.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/osdagbridge/desktop/ui/dialogs/__pycache__/resources/vectors/arrow_up_dark.svg b/src/osdagbridge/desktop/ui/dialogs/__pycache__/resources/vectors/arrow_up_dark.svg new file mode 100644 index 00000000..34f7193e --- /dev/null +++ b/src/osdagbridge/desktop/ui/dialogs/__pycache__/resources/vectors/arrow_up_dark.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/osdagbridge/desktop/ui/dialogs/__pycache__/resources/vectors/arrow_up_light.svg b/src/osdagbridge/desktop/ui/dialogs/__pycache__/resources/vectors/arrow_up_light.svg new file mode 100644 index 00000000..494d7624 --- /dev/null +++ b/src/osdagbridge/desktop/ui/dialogs/__pycache__/resources/vectors/arrow_up_light.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/osdagbridge/desktop/ui/dialogs/__pycache__/resources/vectors/checked.svg b/src/osdagbridge/desktop/ui/dialogs/__pycache__/resources/vectors/checked.svg new file mode 100644 index 00000000..829f9eda --- /dev/null +++ b/src/osdagbridge/desktop/ui/dialogs/__pycache__/resources/vectors/checked.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/osdagbridge/desktop/ui/dialogs/__pycache__/resources/vectors/design.svg b/src/osdagbridge/desktop/ui/dialogs/__pycache__/resources/vectors/design.svg new file mode 100644 index 00000000..a5085160 --- /dev/null +++ b/src/osdagbridge/desktop/ui/dialogs/__pycache__/resources/vectors/design.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/osdagbridge/desktop/ui/dialogs/__pycache__/resources/vectors/design_report.svg b/src/osdagbridge/desktop/ui/dialogs/__pycache__/resources/vectors/design_report.svg new file mode 100644 index 00000000..5c224e91 --- /dev/null +++ b/src/osdagbridge/desktop/ui/dialogs/__pycache__/resources/vectors/design_report.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/osdagbridge/desktop/ui/dialogs/__pycache__/resources/vectors/input_dock_active_light.svg b/src/osdagbridge/desktop/ui/dialogs/__pycache__/resources/vectors/input_dock_active_light.svg new file mode 100644 index 00000000..70151046 --- /dev/null +++ b/src/osdagbridge/desktop/ui/dialogs/__pycache__/resources/vectors/input_dock_active_light.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/osdagbridge/desktop/ui/dialogs/__pycache__/resources/vectors/input_dock_inactive_light.svg b/src/osdagbridge/desktop/ui/dialogs/__pycache__/resources/vectors/input_dock_inactive_light.svg new file mode 100644 index 00000000..32bfa48d --- /dev/null +++ b/src/osdagbridge/desktop/ui/dialogs/__pycache__/resources/vectors/input_dock_inactive_light.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/osdagbridge/desktop/ui/dialogs/__pycache__/resources/vectors/inputs_label_light.svg b/src/osdagbridge/desktop/ui/dialogs/__pycache__/resources/vectors/inputs_label_light.svg new file mode 100644 index 00000000..4306a92a --- /dev/null +++ b/src/osdagbridge/desktop/ui/dialogs/__pycache__/resources/vectors/inputs_label_light.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/osdagbridge/desktop/ui/dialogs/__pycache__/resources/vectors/lock_close.svg b/src/osdagbridge/desktop/ui/dialogs/__pycache__/resources/vectors/lock_close.svg new file mode 100644 index 00000000..8495792d --- /dev/null +++ b/src/osdagbridge/desktop/ui/dialogs/__pycache__/resources/vectors/lock_close.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/osdagbridge/desktop/ui/dialogs/__pycache__/resources/vectors/lock_open.svg b/src/osdagbridge/desktop/ui/dialogs/__pycache__/resources/vectors/lock_open.svg new file mode 100644 index 00000000..5f22789a --- /dev/null +++ b/src/osdagbridge/desktop/ui/dialogs/__pycache__/resources/vectors/lock_open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/osdagbridge/desktop/ui/dialogs/__pycache__/resources/vectors/lock_open_light.svg b/src/osdagbridge/desktop/ui/dialogs/__pycache__/resources/vectors/lock_open_light.svg new file mode 100644 index 00000000..5f22789a --- /dev/null +++ b/src/osdagbridge/desktop/ui/dialogs/__pycache__/resources/vectors/lock_open_light.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/osdagbridge/desktop/ui/dialogs/__pycache__/resources/vectors/logs_dock_active_light.svg b/src/osdagbridge/desktop/ui/dialogs/__pycache__/resources/vectors/logs_dock_active_light.svg new file mode 100644 index 00000000..5d23c855 --- /dev/null +++ b/src/osdagbridge/desktop/ui/dialogs/__pycache__/resources/vectors/logs_dock_active_light.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/osdagbridge/desktop/ui/dialogs/__pycache__/resources/vectors/logs_dock_inactive_light.svg b/src/osdagbridge/desktop/ui/dialogs/__pycache__/resources/vectors/logs_dock_inactive_light.svg new file mode 100644 index 00000000..56e27348 --- /dev/null +++ b/src/osdagbridge/desktop/ui/dialogs/__pycache__/resources/vectors/logs_dock_inactive_light.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/osdagbridge/desktop/ui/dialogs/__pycache__/resources/vectors/output_dock_active_light.svg b/src/osdagbridge/desktop/ui/dialogs/__pycache__/resources/vectors/output_dock_active_light.svg new file mode 100644 index 00000000..7ef0439c --- /dev/null +++ b/src/osdagbridge/desktop/ui/dialogs/__pycache__/resources/vectors/output_dock_active_light.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/osdagbridge/desktop/ui/dialogs/__pycache__/resources/vectors/output_dock_inactive_light.svg b/src/osdagbridge/desktop/ui/dialogs/__pycache__/resources/vectors/output_dock_inactive_light.svg new file mode 100644 index 00000000..a8e65d4a --- /dev/null +++ b/src/osdagbridge/desktop/ui/dialogs/__pycache__/resources/vectors/output_dock_inactive_light.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/osdagbridge/desktop/ui/dialogs/__pycache__/resources/vectors/outputs_label_light.svg b/src/osdagbridge/desktop/ui/dialogs/__pycache__/resources/vectors/outputs_label_light.svg new file mode 100644 index 00000000..cfddf439 --- /dev/null +++ b/src/osdagbridge/desktop/ui/dialogs/__pycache__/resources/vectors/outputs_label_light.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/osdagbridge/desktop/ui/dialogs/__pycache__/resources/vectors/save.svg b/src/osdagbridge/desktop/ui/dialogs/__pycache__/resources/vectors/save.svg new file mode 100644 index 00000000..c86072a9 --- /dev/null +++ b/src/osdagbridge/desktop/ui/dialogs/__pycache__/resources/vectors/save.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/osdagbridge/desktop/ui/docks/input_dock.py b/src/osdagbridge/desktop/ui/docks/input_dock.py index 310e1961..cb5d9b2a 100644 --- a/src/osdagbridge/desktop/ui/docks/input_dock.py +++ b/src/osdagbridge/desktop/ui/docks/input_dock.py @@ -976,570 +976,11 @@ def build_left_panel(self, field_list): group_container_layout.setContentsMargins(0, 0, 0, 0) group_container_layout.setSpacing(12) + self.section_contexts = {} + self.superstructure_body_layout = None - # === Type of Structure Box === - type_box = QGroupBox("Type of Structure") - type_box.setStyleSheet(""" - QGroupBox { - border: 1px solid #90AF13; - border-radius: 4px; - background-color: white; - padding: 8px; - margin-top: 12px; - font-size: 10px; - font-weight: bold; - color: #333; - } - QGroupBox::title { - subcontrol-origin: margin; - subcontrol-position: top left; - left: 8px; - padding: 0 4px; - margin-top: 4px; - background-color: white; - color: #333; - } - """) - type_box_layout = QVBoxLayout(type_box) - type_box_layout.setContentsMargins(8, 8, 8, 8) - type_box_layout.setSpacing(8) - - # Type of Structure field - type_row = QHBoxLayout() - type_field_label = QLabel("Type of Structure") - type_field_label.setStyleSheet(""" - QLabel { - color: #000000; - font-size: 12px; - background: transparent; - } - """) - type_field_label.setMinimumWidth(110) - - self.structure_type_combo = NoScrollComboBox() - self.structure_type_combo.setObjectName(KEY_STRUCTURE_TYPE) - apply_field_style(self.structure_type_combo) - self.structure_type_combo.addItems(VALUES_STRUCTURE_TYPE) - - type_row.addWidget(type_field_label) - type_row.addWidget(self.structure_type_combo, 1) - type_box_layout.addLayout(type_row) - - self.structure_note = QLabel("*Other structures not included") - self.structure_note.setStyleSheet(""" - QLabel { - color: #000000; - font-size: 12px; - background: transparent; - } - """) - self.structure_note.setVisible(False) - type_box_layout.addWidget(self.structure_note) - - self.structure_type_combo.currentTextChanged.connect(self.on_structure_type_changed) - group_container_layout.addWidget(type_box) - - # === Project Location Box === - location_box = QGroupBox() - location_box.setStyleSheet(""" - QGroupBox { - border: 1px solid #90AF13; - border-radius: 4px; - background-color: white; - padding: 8px; - margin-top: 12px; - } - """) - location_box_layout = QVBoxLayout(location_box) - location_box_layout.setContentsMargins(8, 8, 8, 8) - location_box_layout.setSpacing(8) - - loc_header = QHBoxLayout() - loc_title = QLabel("Project Location*") - loc_title.setStyleSheet(""" - QLabel { - color: #000000; - font-size: 12px; - background: transparent; - } - """) - loc_title.setMinimumWidth(110) - loc_header.addWidget(loc_title) - - add_here_btn = QPushButton("Add Here") - add_here_btn.setCursor(Qt.CursorShape.PointingHandCursor) - add_here_btn.setStyleSheet(""" - QPushButton { - background-color: #90AF13; - color: white; - font-weight: bold; - border: none; - border-radius: 4px; - padding: 8px 20px; - font-size: 11px; - min-width: 80px; - } - QPushButton:hover { - background-color: #7a9a12; - } - QPushButton:disabled{ - background: #D0D0D0; - color: #666; - } - - """) - add_here_btn.clicked.connect(self.show_project_location_dialog) - loc_header.addWidget(add_here_btn, 1) - location_box_layout.addLayout(loc_header) - group_container_layout.addWidget(location_box) - - # === Superstructure Section (Contains Everything) === - structure_group = QGroupBox() - structure_group.setStyleSheet(""" - QGroupBox { - border: 1px solid #90AF13; - border-radius: 5px; - margin-top: 0px; - padding-top: 5px; - background-color: white; - } - """) - structure_layout = QVBoxLayout() - structure_layout.setContentsMargins(10, 10, 10, 10) - structure_layout.setSpacing(10) - - # Header with title and collapse icon - struct_header = QHBoxLayout() - struct_title = QLabel("Superstructure") - struct_title.setStyleSheet("font-size: 13px; font-weight: bold; color: #333;") - struct_header.addWidget(struct_title) - struct_header.addStretch() - - # Collapse/expand toggle using SVG icon - toggle_btn = QPushButton() - toggle_btn.setCursor(Qt.CursorShape.PointingHandCursor) - toggle_btn.setCheckable(True) - toggle_btn.setChecked(True) - toggle_btn.setIcon(QIcon(":/vectors/arrow_up_light.svg")) - toggle_btn.setIconSize(QSize(20, 20)) - toggle_btn.setStyleSheet(""" - QPushButton { - background: transparent; - border: none; - padding: 2px; - } - QPushButton:hover { - background: transparent; - } - QPushButton:pressed { - background: transparent; - } - """) - struct_header.addWidget(toggle_btn) - - structure_layout.addLayout(struct_header) - - # body widget that contains everything inside the Superstructure and can be hidden - structure_body = QFrame() - structure_body.setFrameShape(QFrame.NoFrame) - structure_body_layout = QVBoxLayout(structure_body) - structure_body_layout.setContentsMargins(0, 0, 0, 0) - structure_body_layout.setSpacing(10) - structure_body.setVisible(True) - structure_layout.addWidget(structure_body) - - def _toggle_structure(checked): - # checked True means show body (open) - structure_body.setVisible(checked) - toggle_btn.setIcon(QIcon(":/vectors/arrow_up_light.svg" if checked else ":/vectors/arrow_down_light.svg")) - - toggle_btn.toggled.connect(_toggle_structure) - - # === Geometric Details Box === - geo_box = QGroupBox("Geometric Details") - geo_box.setStyleSheet(""" - QGroupBox { - border: 1px solid #90AF13; - border-radius: 4px; - background-color: white; - padding: 8px; - margin-top: 12px; - font-size: 10px; - font-weight: bold; - color: #333; - } - QGroupBox::title { - subcontrol-origin: margin; - subcontrol-position: top left; - left: 8px; - padding: 0 4px; - margin-top: 4px; - background-color: white; - color: #333; - } - """) - geo_box_layout = QVBoxLayout(geo_box) - geo_box_layout.setContentsMargins(8, 8, 8, 8) - geo_box_layout.setSpacing(8) - - # Span - span_row = QHBoxLayout() - span_label = QLabel("Span*") - span_label.setStyleSheet(""" - QLabel { - color: #000000; - font-size: 12px; - background: transparent; - } - """) - span_label.setMinimumWidth(110) - self.span_input = QLineEdit() - self.span_input.setObjectName(KEY_SPAN) - apply_field_style(self.span_input) - self.span_input.setValidator(QDoubleValidator(SPAN_MIN, SPAN_MAX, 2)) - self.span_input.setPlaceholderText(f"{SPAN_MIN}-{SPAN_MAX} m") - span_row.addWidget(span_label) - span_row.addWidget(self.span_input, 1) - geo_box_layout.addLayout(span_row) - - # Carriageway Width - carriageway_row = QHBoxLayout() - carriageway_label = QLabel("Carriageway Width*") - carriageway_label.setStyleSheet(""" - QLabel { - color: #000000; - font-size: 12px; - background: transparent; - } - """) - carriageway_label.setMinimumWidth(110) - self.carriageway_input = QLineEdit() - self.carriageway_input.setObjectName(KEY_CARRIAGEWAY_WIDTH) - apply_field_style(self.carriageway_input) - self.carriageway_input.setValidator(QDoubleValidator(0.0, 100.0, 2)) - self.carriageway_input.editingFinished.connect(self.validate_carriageway_width) - carriageway_row.addWidget(carriageway_label) - carriageway_row.addWidget(self.carriageway_input, 1) - geo_box_layout.addLayout(carriageway_row) - - # Include Median option - median_row = QHBoxLayout() - median_row.setContentsMargins(0, 0, 0, 0) - median_row.setSpacing(8) - - median_label = QLabel("Include Median") - median_label.setStyleSheet(""" - QLabel { - color: #000000; - font-size: 12px; - background: transparent; - } - """) - median_label.setMinimumWidth(110) - median_row.addWidget(median_label) - - self.include_median_combo = NoScrollComboBox() - self.include_median_combo.addItems(["No", "Yes"]) - self.include_median_combo.setCurrentIndex(0) - self.include_median_combo.setObjectName(KEY_INCLUDE_MEDIAN) - apply_field_style(self.include_median_combo) - #self.include_median_combo.setMaximumWidth(110) - self.include_median_combo.currentTextChanged.connect(self.on_include_median_changed) - median_row.addWidget(self.include_median_combo, 1) - median_row.addStretch() - geo_box_layout.addLayout(median_row) - self._update_carriageway_placeholder() - - # Footpath - footpath_row = QHBoxLayout() - footpath_label = QLabel("Footpath") - footpath_label.setStyleSheet(""" - QLabel { - color: #000000; - font-size: 12px; - background: transparent; - } - """) - footpath_label.setMinimumWidth(110) - self.footpath_combo = NoScrollComboBox() - self.footpath_combo.setObjectName(KEY_FOOTPATH) - apply_field_style(self.footpath_combo) - self.footpath_combo.addItems(VALUES_FOOTPATH) - self.footpath_combo.setCurrentIndex(0) - self.footpath_combo.currentTextChanged.connect(self.on_footpath_changed) - footpath_row.addWidget(footpath_label) - footpath_row.addWidget(self.footpath_combo, 1) - geo_box_layout.addLayout(footpath_row) - - # Skew Angle - skew_row = QHBoxLayout() - skew_label = QLabel("Skew Angle") - skew_label.setStyleSheet(""" - QLabel { - color: #000000; - font-size: 12px; - background: transparent; - } - """) - skew_label.setMinimumWidth(110) - self.skew_input = QLineEdit() - self.skew_input.setObjectName(KEY_SKEW_ANGLE) - apply_field_style(self.skew_input) - self.skew_input.setValidator(QDoubleValidator(SKEW_ANGLE_MIN, SKEW_ANGLE_MAX, 1)) - #self.skew_input.setText(f"{str(SKEW_ANGLE_DEFAULT)}°") - self.skew_input.setPlaceholderText(f"{SKEW_ANGLE_MIN} - {SKEW_ANGLE_MAX}°") - skew_row.addWidget(skew_label) - skew_row.addWidget(self.skew_input, 1) - geo_box_layout.addLayout(skew_row) - - # Additional Geometry (inside Geometric Details) - add_geo_row = QHBoxLayout() - add_geo_label = QLabel("Additional Geometry") - add_geo_label.setStyleSheet(""" - QLabel { - color: #000000; - font-size: 12px; - background: transparent; - } - """) - add_geo_label.setMinimumWidth(110) - add_geo_row.addWidget(add_geo_label) - - modify_geo_btn = QPushButton("Modify Here") - modify_geo_btn.setCursor(Qt.CursorShape.PointingHandCursor) - modify_geo_btn.setStyleSheet(""" - QPushButton { - background-color: #90AF13; - color: white; - font-weight: bold; - border: none; - border-radius: 4px; - padding: 8px 20px; - font-size: 11px; - min-width: 80px; - } - QPushButton:hover { - background-color: #7a9a12; - } - QPushButton:disabled{ - background: #D0D0D0; - color: #666; - } - """) - modify_geo_btn.clicked.connect(self.show_additional_inputs) - add_geo_row.addWidget(modify_geo_btn, 1) - geo_box_layout.addLayout(add_geo_row) - - structure_body_layout.addWidget(geo_box) - - # === Material Inputs Box === - material_box = QGroupBox("Material Inputs") - material_box.setStyleSheet(""" - QGroupBox { - border: 1px solid #90AF13; - border-radius: 4px; - background-color: white; - padding: 8px; - margin-top: 12px; - font-size: 10px; - font-weight: bold; - color: #333; - } - QGroupBox::title { - subcontrol-origin: margin; - subcontrol-position: top left; - left: 8px; - padding: 0 4px; - margin-top: 4px; - background-color: white; - color: #333; - } - """) - material_box_layout = QVBoxLayout(material_box) - material_box_layout.setContentsMargins(8, 8, 8, 8) - material_box_layout.setSpacing(8) - - # Girder - girder_row = QHBoxLayout() - girder_label = QLabel("Girder") - girder_label.setStyleSheet(""" - QLabel { - color: #000000; - font-size: 12px; - background: transparent; - } - """) - girder_label.setMinimumWidth(110) - self.girder_combo = NoScrollComboBox() - self.girder_combo.setObjectName(KEY_GIRDER) - apply_field_style(self.girder_combo) - self.girder_combo.addItems(VALUES_MATERIAL) - girder_row.addWidget(girder_label) - girder_row.addWidget(self.girder_combo, 1) - material_box_layout.addLayout(girder_row) - - # Cross Bracing - cross_bracing_row = QHBoxLayout() - cross_bracing_label = QLabel("Cross Bracing") - cross_bracing_label.setStyleSheet(""" - QLabel { - color: #000000; - font-size: 12px; - background: transparent; - } - """) - cross_bracing_label.setMinimumWidth(110) - self.cross_bracing_combo = NoScrollComboBox() - self.cross_bracing_combo.setObjectName(KEY_CROSS_BRACING) - apply_field_style(self.cross_bracing_combo) - self.cross_bracing_combo.addItems(VALUES_MATERIAL) - cross_bracing_row.addWidget(cross_bracing_label) - cross_bracing_row.addWidget(self.cross_bracing_combo, 1) - material_box_layout.addLayout(cross_bracing_row) - - # End Diaphragm - end_diaphragm_row = QHBoxLayout() - end_diaphragm_label = QLabel("End Diaphragm") - end_diaphragm_label.setStyleSheet(""" - QLabel { - color: #000000; - font-size: 12px; - background: transparent; - } - """) - end_diaphragm_label.setMinimumWidth(110) - self.end_diaphragm_combo = NoScrollComboBox() - self.end_diaphragm_combo.setObjectName(KEY_END_DIAPHRAGM) - apply_field_style(self.end_diaphragm_combo) - self.end_diaphragm_combo.addItems(VALUES_MATERIAL) - end_diaphragm_row.addWidget(end_diaphragm_label) - end_diaphragm_row.addWidget(self.end_diaphragm_combo, 1) - material_box_layout.addLayout(end_diaphragm_row) - - # Deck - deck_row = QHBoxLayout() - deck_label = QLabel("Deck") - deck_label.setStyleSheet(""" - QLabel { - color: #000000; - font-size: 12px; - background: transparent; - } - """) - deck_label.setMinimumWidth(110) - self.deck_combo = NoScrollComboBox() - self.deck_combo.setObjectName(KEY_DECK_CONCRETE_GRADE_BASIC) - apply_field_style(self.deck_combo) - self.deck_combo.addItems(VALUES_DECK_CONCRETE_GRADE) - self.deck_combo.setCurrentText("M 25") - deck_row.addWidget(deck_label) - deck_row.addWidget(self.deck_combo, 1) - material_box_layout.addLayout(deck_row) - - # Material Properties header with button - mat_prop_header = QHBoxLayout() - mat_prop_title = QLabel("Properties") - mat_prop_title.setStyleSheet(""" - QLabel { - color: #000000; - font-size: 12px; - background: transparent; - } - """) - mat_prop_title.setMinimumWidth(110) - mat_prop_header.addWidget(mat_prop_title) - - modify_mat_btn = QPushButton("Modify Here") - modify_mat_btn.setCursor(Qt.CursorShape.PointingHandCursor) - modify_mat_btn.setStyleSheet(""" - QPushButton { - background-color: #90AF13; - color: white; - font-weight: bold; - border: none; - border-radius: 4px; - padding: 8px 20px; - font-size: 11px; - min-width: 80px; - } - QPushButton:hover { - background-color: #7a9a12; - } - QPushButton:disabled{ - background: #f1f1f1; - color: #666; - } - """) - modify_mat_btn.clicked.connect(self.show_material_properties_dialog) - mat_prop_header.addWidget(modify_mat_btn, 1) - material_box_layout.addLayout(mat_prop_header) - - structure_body_layout.addWidget(material_box) - - # Close the Superstructure section - structure_group.setLayout(structure_layout) - group_container_layout.addWidget(structure_group) - - # === Substructure Section (empty body for now) === - sub_group = QGroupBox() - sub_group.setStyleSheet(""" - QGroupBox { - border: 1px solid #90AF13; - border-radius: 5px; - margin-top: 8px; - padding-top: 5px; - background-color: white; - } - """) - sub_layout = QVBoxLayout() - sub_layout.setContentsMargins(10, 10, 10, 10) - sub_layout.setSpacing(8) - - # Header with toggle - sub_header = QHBoxLayout() - sub_title = QLabel("Substructure") - sub_title.setStyleSheet("font-size: 13px; font-weight: bold; color: #333;") - sub_header.addWidget(sub_title) - sub_header.addStretch() - - sub_toggle = QPushButton() - sub_toggle.setCursor(Qt.CursorShape.PointingHandCursor) - sub_toggle.setCheckable(True) - sub_toggle.setChecked(True) - sub_toggle.setIcon(QIcon(":/vectors/arrow_up_light.svg")) - sub_toggle.setIconSize(QSize(20, 20)) - sub_toggle.setStyleSheet(""" - QPushButton { - background: transparent; - border: none; - padding: 2px; - } - QPushButton:hover { - background: transparent; - } - QPushButton:pressed { - background: transparent; - } - """) - sub_header.addWidget(sub_toggle) - sub_layout.addLayout(sub_header) - - sub_body = QFrame() - sub_body.setFrameShape(QFrame.NoFrame) - sub_body_layout = QVBoxLayout(sub_body) - sub_body_layout.setContentsMargins(0,0,0,0) - sub_body_layout.setSpacing(6) - sub_body.setVisible(True) - sub_layout.addWidget(sub_body) - - def _toggle_sub(checked): - sub_body.setVisible(checked) - sub_toggle.setIcon(QIcon(":/vectors/arrow_up_light.svg" if checked else ":/vectors/arrow_down_light.svg")) - - sub_toggle.toggled.connect(_toggle_sub) - - sub_group.setLayout(sub_layout) - group_container_layout.addWidget(sub_group) + self._build_basic_inputs(field_list, group_container_layout) + self._add_substructure_section(group_container_layout) group_container_layout.addStretch() scroll_area.setWidget(group_container) @@ -1597,6 +1038,479 @@ def _toggle_sub(checked): left_layout.addWidget(h_scroll_area) self._apply_lock_state() + + def _build_basic_inputs(self, field_definitions, root_layout): + current_section_id = None + for definition in field_definitions: + key, label, field_type, values, required, validator, metadata = self._normalize_definition(definition) + if field_type == TYPE_MODULE: + continue + if field_type == TYPE_TITLE: + section_id = key or label + section_context = self._create_section_context(section_id, label, metadata, root_layout) + current_section_id = section_context["id"] + continue + if current_section_id is None: + continue + section_context = self.section_contexts.get(current_section_id) + if not section_context: + continue + self._create_field_row(section_context, key, label, field_type, values, validator, metadata) + + self._finalize_section_contexts() + self._update_carriageway_placeholder() + + def _normalize_definition(self, definition): + if len(definition) == 6: + return (*definition, {}) + return definition + + def _create_section_context(self, section_id, title, metadata, root_layout): + container_key = (metadata or {}).get("container", "main") + parent_layout = self._get_container_layout(container_key, root_layout) + + show_title = metadata.get("show_group_title", True) if metadata else True + group_title = title if show_title and title else "" + group_box = QGroupBox(group_title) if group_title else QGroupBox() + group_box.setStyleSheet(self._section_groupbox_style()) + + layout = QVBoxLayout(group_box) + layout.setContentsMargins(8, 8, 8, 8) + layout.setSpacing(8) + + if metadata and metadata.get("custom_content") == "project_location": + self._add_project_location_controls(layout, metadata) + + parent_layout.addWidget(group_box) + context = { + "id": section_id, + "layout": layout, + "metadata": metadata or {}, + "group_box": group_box, + } + self.section_contexts[section_id] = context + return context + + def _section_groupbox_style(self): + return ( + "QGroupBox {\n" + " border: 1px solid #90AF13;\n" + " border-radius: 4px;\n" + " background-color: white;\n" + " padding: 8px;\n" + " margin-top: 12px;\n" + " font-size: 10px;\n" + " font-weight: bold;\n" + " color: #333;\n" + "}\n" + "QGroupBox::title {\n" + " subcontrol-origin: margin;\n" + " subcontrol-position: top left;\n" + " left: 8px;\n" + " padding: 0 4px;\n" + " margin-top: 4px;\n" + " background-color: white;\n" + " color: #333;\n" + "}" + ) + + def _get_container_layout(self, container_key, root_layout): + if container_key == "superstructure": + if self.superstructure_body_layout is None: + self.superstructure_body_layout = self._create_superstructure_group(root_layout) + return self.superstructure_body_layout + return root_layout + + def _create_superstructure_group(self, root_layout): + structure_group = QGroupBox() + structure_group.setStyleSheet( + "QGroupBox {\n" + " border: 1px solid #90AF13;\n" + " border-radius: 5px;\n" + " margin-top: 0px;\n" + " padding-top: 5px;\n" + " background-color: white;\n" + "}" + ) + structure_layout = QVBoxLayout() + structure_layout.setContentsMargins(10, 10, 10, 10) + structure_layout.setSpacing(10) + + header = QHBoxLayout() + title = QLabel("Superstructure") + title.setStyleSheet("font-size: 13px; font-weight: bold; color: #333;") + header.addWidget(title) + header.addStretch() + + toggle_btn = QPushButton() + toggle_btn.setCursor(Qt.CursorShape.PointingHandCursor) + toggle_btn.setCheckable(True) + toggle_btn.setChecked(True) + toggle_btn.setIcon(QIcon(":/vectors/arrow_up_light.svg")) + toggle_btn.setIconSize(QSize(20, 20)) + toggle_btn.setStyleSheet( + "QPushButton {\n" + " background: transparent;\n" + " border: none;\n" + " padding: 2px;\n" + "}\n" + "QPushButton:hover {\n" + " background: transparent;\n" + "}\n" + "QPushButton:pressed {\n" + " background: transparent;\n" + "}" + ) + header.addWidget(toggle_btn) + structure_layout.addLayout(header) + + structure_body = QFrame() + structure_body.setFrameShape(QFrame.NoFrame) + body_layout = QVBoxLayout(structure_body) + body_layout.setContentsMargins(0, 0, 0, 0) + body_layout.setSpacing(10) + structure_body.setVisible(True) + structure_layout.addWidget(structure_body) + + def _toggle(checked): + structure_body.setVisible(checked) + toggle_btn.setIcon(QIcon(":/vectors/arrow_up_light.svg" if checked else ":/vectors/arrow_down_light.svg")) + + toggle_btn.toggled.connect(_toggle) + + structure_group.setLayout(structure_layout) + root_layout.addWidget(structure_group) + return body_layout + + def _add_project_location_controls(self, layout, metadata): + label_text = metadata.get("header_label") or "Project Location*" + button_rows = metadata.get("button_rows") + if button_rows: + for row_entry in button_rows: + row_config = self._prepare_button_row_config(row_entry, {"label": label_text}) + if row_config: + self._add_button_row(layout, row_config) + return + + fallback_row = self._prepare_button_row_config("project_location", {"label": label_text}) + self._add_button_row(layout, fallback_row) + + def _section_label_style(self): + return ( + "QLabel {\n" + " color: #000000;\n" + " font-size: 12px;\n" + " background: transparent;\n" + "}" + ) + + def _default_action_button_style(self): + return ( + "QPushButton {\n" + " background-color: #90AF13;\n" + " color: white;\n" + " font-weight: bold;\n" + " border: none;\n" + " border-radius: 4px;\n" + " padding: 8px 20px;\n" + " font-size: 11px;\n" + " min-width: 80px;\n" + "}\n" + "QPushButton:hover {\n" + " background-color: #7a9a12;\n" + "}\n" + "QPushButton:disabled{\n" + " background: #D0D0D0;\n" + " color: #666;\n" + "}" + ) + + def _default_row_config(self, row_type): + mapping = { + "project_location": { + "label": "Project Location*", + "buttons": [ + {"text": "Add Here", "action": "show_project_location_dialog"}, + ], + }, + "additional_geometry": { + "label": "Additional Geometry", + "buttons": [ + {"text": "Modify Here", "action": "show_additional_inputs"}, + ], + }, + "material_properties": { + "label": "Properties", + "buttons": [ + {"text": "Modify Here", "action": "show_material_properties_dialog"}, + ], + }, + } + return mapping.get(row_type, {}) + + def _prepare_button_row_config(self, config_entry, fallback_defaults=None): + fallback_defaults = fallback_defaults or {} + if isinstance(config_entry, str): + config = {"type": config_entry} + else: + config = dict(config_entry or {}) + + row_type = config.get("type") + defaults = self._default_row_config(row_type) + + resolved = {} + resolved.update(defaults) + resolved.update(fallback_defaults) + resolved.update(config) + + if not resolved.get("buttons"): + extra_defaults = self._default_row_config(resolved.get("type")) + if extra_defaults: + resolved.setdefault("buttons", extra_defaults.get("buttons")) + resolved.setdefault("label", extra_defaults.get("label")) + + return resolved if resolved.get("buttons") else None + + def _add_button_row(self, layout, config): + if not config: + return + + row = QHBoxLayout() + row.setContentsMargins(0, 0, 0, 0) + row.setSpacing(8) + + label_text = config.get("label") + if label_text: + field_label = QLabel(label_text) + field_label.setStyleSheet(self._section_label_style()) + field_label.setMinimumWidth(config.get("label_min_width", 110)) + row.addWidget(field_label) + + buttons = config.get("buttons", []) + for button_config in buttons: + button = self._create_action_button(button_config) + stretch = button_config.get("stretch", 1 if len(buttons) == 1 else 0) + row.addWidget(button, stretch) + + if config.get("add_stretch", True): + row.addStretch() + + layout.addLayout(row) + + def _create_action_button(self, config): + button = QPushButton(config.get("text", "Action")) + button.setCursor(Qt.CursorShape.PointingHandCursor) + if config.get("size_policy") == "fixed": + button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) + else: + button.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) + + icon_path = config.get("icon") + if icon_path: + button.setIcon(QIcon(icon_path)) + icon_size = config.get("icon_size") + if isinstance(icon_size, (list, tuple)) and len(icon_size) == 2: + button.setIconSize(QSize(icon_size[0], icon_size[1])) + + style = config.get("style") or self._default_action_button_style() + button.setStyleSheet(style) + + tooltip = config.get("tooltip") + if tooltip: + button.setToolTip(tooltip) + + action_name = config.get("action") + callback = getattr(self, action_name, None) if action_name else None + if callable(callback): + button.clicked.connect(callback) + else: + button.setEnabled(False) + + return button + + def _create_field_row(self, section_context, key, label, field_type, values, validator, metadata): + widget = self._create_input_widget(key, field_type, values, validator, metadata) + if widget is None: + return + row = QHBoxLayout() + row.setContentsMargins(0, 0, 0, 0) + row.setSpacing(8) + display_label = (metadata or {}).get("label") if metadata else None + field_label = QLabel(display_label or label) + field_label.setStyleSheet(self._section_label_style()) + field_label.setMinimumWidth(110) + row.addWidget(field_label) + row.addWidget(widget, 1) + if metadata.get("add_stretch"): + row.addStretch() + section_context["layout"].addLayout(row) + + def _create_input_widget(self, key, field_type, values, validator, metadata): + if field_type == TYPE_COMBOBOX: + widget = NoScrollComboBox() + apply_field_style(widget) + if values: + widget.addItems(values) + default_value = (metadata or {}).get("default") + if default_value: + idx = widget.findText(default_value) + if idx >= 0: + widget.setCurrentIndex(idx) + elif field_type == TYPE_TEXTBOX: + widget = QLineEdit() + apply_field_style(widget) + validator_instance = self.get_validator(validator) + if validator_instance: + widget.setValidator(validator_instance) + else: + return None + + if key: + widget.setObjectName(key) + self._register_input_widget(key, widget) + self._apply_field_specific_config(key, widget, metadata or {}) + return widget + + def _register_input_widget(self, key, widget): + if key == KEY_STRUCTURE_TYPE: + self.structure_type_combo = widget + elif key == KEY_SPAN: + self.span_input = widget + elif key == KEY_CARRIAGEWAY_WIDTH: + self.carriageway_input = widget + elif key == KEY_INCLUDE_MEDIAN: + self.include_median_combo = widget + elif key == KEY_FOOTPATH: + self.footpath_combo = widget + elif key == KEY_SKEW_ANGLE: + self.skew_input = widget + elif key == KEY_GIRDER: + self.girder_combo = widget + elif key == KEY_CROSS_BRACING: + self.cross_bracing_combo = widget + elif key == KEY_END_DIAPHRAGM: + self.end_diaphragm_combo = widget + elif key == KEY_DECK_CONCRETE_GRADE_BASIC: + self.deck_combo = widget + + def _apply_field_specific_config(self, key, widget, metadata): + if not key or widget is None: + return + if key == KEY_STRUCTURE_TYPE and hasattr(widget, "currentTextChanged"): + widget.currentTextChanged.connect(self.on_structure_type_changed) + elif key == KEY_SPAN and isinstance(widget, QLineEdit): + widget.setValidator(QDoubleValidator(SPAN_MIN, SPAN_MAX, 2)) + widget.setPlaceholderText(f"{SPAN_MIN}-{SPAN_MAX} m") + elif key == KEY_CARRIAGEWAY_WIDTH and isinstance(widget, QLineEdit): + widget.setValidator(QDoubleValidator(0.0, 100.0, 2)) + widget.editingFinished.connect(self.validate_carriageway_width) + elif key == KEY_INCLUDE_MEDIAN and hasattr(widget, "currentTextChanged"): + widget.currentTextChanged.connect(self.on_include_median_changed) + default_value = metadata.get("default") + if default_value: + idx = widget.findText(default_value) + if idx >= 0: + widget.setCurrentIndex(idx) + elif key == KEY_FOOTPATH and hasattr(widget, "currentTextChanged"): + widget.currentTextChanged.connect(self.on_footpath_changed) + default_value = metadata.get("default") + if default_value: + idx = widget.findText(default_value) + if idx >= 0: + widget.setCurrentIndex(idx) + elif key == KEY_SKEW_ANGLE and isinstance(widget, QLineEdit): + widget.setValidator(QDoubleValidator(SKEW_ANGLE_MIN, SKEW_ANGLE_MAX, 1)) + widget.setPlaceholderText(f"{SKEW_ANGLE_MIN} - {SKEW_ANGLE_MAX}°") + elif key == KEY_DECK_CONCRETE_GRADE_BASIC and hasattr(widget, "findText"): + default_value = metadata.get("default") + if default_value: + idx = widget.findText(default_value) + if idx >= 0: + widget.setCurrentIndex(idx) + + def _finalize_section_contexts(self): + for context in self.section_contexts.values(): + metadata = context.get("metadata", {}) + note_config = metadata.get("post_note") + if note_config: + self._add_section_note(context, note_config) + + for row_entry in metadata.get("post_rows", []): + row_config = self._prepare_button_row_config(row_entry) + if row_config: + self._add_button_row(context["layout"], row_config) + + def _add_section_note(self, context, note_config): + note_label = QLabel(note_config.get("text", "")) + note_label.setStyleSheet(self._section_label_style()) + note_label.setVisible(False) + context["layout"].addWidget(note_label) + attr_name = note_config.get("attr") + if attr_name: + setattr(self, attr_name, note_label) + + + def _add_substructure_section(self, parent_layout): + sub_group = QGroupBox() + sub_group.setStyleSheet( + "QGroupBox {\n" + " border: 1px solid #90AF13;\n" + " border-radius: 5px;\n" + " margin-top: 8px;\n" + " padding-top: 5px;\n" + " background-color: white;\n" + "}" + ) + sub_layout = QVBoxLayout() + sub_layout.setContentsMargins(10, 10, 10, 10) + sub_layout.setSpacing(8) + + header = QHBoxLayout() + header.setContentsMargins(0, 0, 0, 0) + header.setSpacing(8) + title = QLabel("Substructure") + title.setStyleSheet("font-size: 13px; font-weight: bold; color: #333;") + header.addWidget(title) + header.addStretch() + + toggle_btn = QPushButton() + toggle_btn.setCursor(Qt.CursorShape.PointingHandCursor) + toggle_btn.setCheckable(True) + toggle_btn.setChecked(True) + toggle_btn.setIcon(QIcon(":/vectors/arrow_up_light.svg")) + toggle_btn.setIconSize(QSize(20, 20)) + toggle_btn.setStyleSheet( + "QPushButton {\n" + " background: transparent;\n" + " border: none;\n" + " padding: 2px;\n" + "}\n" + "QPushButton:hover {\n" + " background: transparent;\n" + "}\n" + "QPushButton:pressed {\n" + " background: transparent;\n" + "}" + ) + header.addWidget(toggle_btn) + sub_layout.addLayout(header) + + sub_body = QFrame() + sub_body.setFrameShape(QFrame.NoFrame) + sub_body_layout = QVBoxLayout(sub_body) + sub_body_layout.setContentsMargins(0, 0, 0, 0) + sub_body_layout.setSpacing(6) + sub_body.setVisible(True) + sub_layout.addWidget(sub_body) + + def _toggle(checked): + sub_body.setVisible(checked) + toggle_btn.setIcon(QIcon(":/vectors/arrow_up_light.svg" if checked else ":/vectors/arrow_down_light.svg")) + + toggle_btn.toggled.connect(_toggle) + + sub_group.setLayout(sub_layout) + parent_layout.addWidget(sub_group) def show_additional_inputs(self): """Show Additional Inputs dialog""" From 9f6c2f754d9af541d6b7b94aa94ac4c5090a311b Mon Sep 17 00:00:00 2001 From: Sreejesh06 Date: Tue, 6 Jan 2026 15:52:31 +0530 Subject: [PATCH 56/59] Enhance OutputDock and RolledSectionPreview for dynamic configuration and improved section rendering --- .../bridge_types/plate_girder/ui_fields.py | 116 +++- .../desktop/ui/docks/output_dock.py | 624 ++++++++++-------- src/osdagbridge/desktop/ui/template_page.py | 2 +- .../ui/utils/rolled_section_preview.py | 157 +++-- 4 files changed, 548 insertions(+), 351 deletions(-) diff --git a/src/osdagbridge/core/bridge_types/plate_girder/ui_fields.py b/src/osdagbridge/core/bridge_types/plate_girder/ui_fields.py index 02989082..11851762 100644 --- a/src/osdagbridge/core/bridge_types/plate_girder/ui_fields.py +++ b/src/osdagbridge/core/bridge_types/plate_girder/ui_fields.py @@ -246,9 +246,119 @@ def set_osdaglogger(self, key): """Logger setup""" print("Logger set up (mock)") - def output_values(self, flag): - """output values List""" - return [] + def output_values(self, flag=None): + """Return output dock definitions using the same tuple structure as input_values.""" + outputs = [] + + outputs.append( + ( + "section_output_analysis", + "Analysis Results", + TYPE_TITLE, + None, + True, + "No Validator", + { + "kind": "analysis", + "fields": [ + ( + "analysis_member", + "Member:", + "combobox", + ["All"], + True, + "No Validator", + {"label_min_width": 100}, + ), + ( + "analysis_load_combination", + "Load Combination:", + "combobox", + ["Envelope"], + True, + "No Validator", + {"label_min_width": 100}, + ), + ( + "analysis_forces", + "Forces", + "checkbox_grid", + [ + ["Fx", "Mx", "Dx"], + ["Fy", "My", "Dy"], + ["Fz", "Mz", "Dz"], + ], + True, + "No Validator", + {}, + ), + ( + "analysis_display_options", + "Display Options:", + "checkbox_row", + ["Max", "Min"], + True, + "No Validator", + {}, + ), + ( + "analysis_utilization", + "Controlling Utilization Ratio", + "checkbox", + None, + True, + "No Validator", + {}, + ), + ], + }, + ) + ) + + outputs.append( + ( + "section_output_superstructure", + "Superstructure", + TYPE_TITLE, + None, + True, + "No Validator", + { + "kind": "design", + "rows": [ + { + "label": "Steel Design", + "buttons": [ + {"text": "Here", "action": "show_additional_inputs"}, + ], + }, + { + "label": "Deck Design", + "buttons": [ + {"text": "Here", "action": "show_additional_inputs"}, + ], + }, + ] + }, + ) + ) + + outputs.append( + ( + "section_output_substructure", + "Substructure", + TYPE_TITLE, + None, + True, + "No Validator", + { + "kind": "design", + "rows": [], + }, + ) + ) + + return outputs def func_for_validation(self, design_inputs): """Validation Function""" diff --git a/src/osdagbridge/desktop/ui/docks/output_dock.py b/src/osdagbridge/desktop/ui/docks/output_dock.py index b678dc40..9ec492fc 100644 --- a/src/osdagbridge/desktop/ui/docks/output_dock.py +++ b/src/osdagbridge/desktop/ui/docks/output_dock.py @@ -1,11 +1,12 @@ from PySide6.QtWidgets import ( QWidget, QVBoxLayout, QHBoxLayout, QLabel, QSizePolicy, - QPushButton, QGroupBox, QCheckBox, QScrollArea, QFrame, QComboBox + QPushButton, QGroupBox, QCheckBox, QScrollArea, QFrame, QComboBox, QLineEdit ) from PySide6.QtCore import Qt, QSize from PySide6.QtGui import QIcon from osdagbridge.desktop.ui.utils.custom_buttons import DockCustomButton +from osdagbridge.core.utils.common import TYPE_TITLE class NoScrollComboBox(QComboBox): def wheelEvent(self, event): @@ -85,10 +86,15 @@ def apply_field_style(widget): class OutputDock(QWidget): """Output dock with collapsible design controls and scrollable layout.""" - def __init__(self, parent): + def __init__(self, backend=None, parent=None): super().__init__() self.parent = parent + self.backend = backend self.setStyleSheet("background: transparent;") + configs = self._load_configs() + self.analysis_config = configs.get("analysis") + # Configurable button rows per section; populated from backend ui_fields + self.section_configs = configs.get("design", []) self.init_ui() def toggle_output_dock(self): @@ -190,90 +196,9 @@ def init_ui(self): scroll_layout.setContentsMargins(0, 0, 0, 0) scroll_layout.setSpacing(10) - results_group = QGroupBox("Analysis Results") - results_group.setStyleSheet( - """ - QGroupBox { - font-weight: bold; - font-size: 11px; - color: #333; - border: 1px solid #90AF13; - border-radius: 4px; - margin-top: 8px; - padding-top: 12px; - background-color: white; - } - QGroupBox::title { - subcontrol-origin: margin; - subcontrol-position: top left; - left: 8px; - padding: 0 4px; - background-color: white; - } - """ - ) - results_layout = QVBoxLayout(results_group) - results_layout.setContentsMargins(10, 8, 10, 10) - results_layout.setSpacing(8) - - member_row = QHBoxLayout() - member_label = QLabel("Member:") - member_label.setStyleSheet("font-size: 10px; color: #333; font-weight: normal;") - member_label.setMinimumWidth(100) - self.member_combo = NoScrollComboBox() - self.member_combo.addItems(["All"]) - apply_field_style(self.member_combo) - member_row.addWidget(member_label) - member_row.addWidget(self.member_combo) - results_layout.addLayout(member_row) - - load_combo_row = QHBoxLayout() - load_combo_label = QLabel("Load Combination:") - load_combo_label.setStyleSheet("font-size: 10px; color: #333; font-weight: normal;") - load_combo_label.setMinimumWidth(100) - self.load_combo = NoScrollComboBox() - self.load_combo.addItems(["Envelope"]) - apply_field_style(self.load_combo) - load_combo_row.addWidget(load_combo_label) - load_combo_row.addWidget(self.load_combo) - results_layout.addLayout(load_combo_row) - - forces_grid = QHBoxLayout() - forces_grid.setSpacing(8) - - col1 = QVBoxLayout() - for text in ["Fx", "Mx", "Dx"]: - cb = QCheckBox(text) - col1.addWidget(cb) - col2 = QVBoxLayout() - for text in ["Fy", "My", "Dy"]: - cb = QCheckBox(text) - col2.addWidget(cb) - col3 = QVBoxLayout() - for text in ["Fz", "Mz", "Dz"]: - cb = QCheckBox(text) - col3.addWidget(cb) - forces_grid.addLayout(col1) - forces_grid.addLayout(col2) - forces_grid.addLayout(col3) - results_layout.addLayout(forces_grid) - - display_label = QLabel("Display Options:") - display_label.setStyleSheet("font-size: 10px; color: #333; font-weight: normal; margin-top: 4px;") - results_layout.addWidget(display_label) - - display_row = QHBoxLayout() - display_row.setSpacing(12) - for text in ["Max", "Min"]: - cb = QCheckBox(text) - display_row.addWidget(cb) - display_row.addStretch() - results_layout.addLayout(display_row) - - utilization_check = QCheckBox("Controlling Utilization Ratio") - results_layout.addWidget(utilization_check) - - scroll_layout.addWidget(results_group) + analysis_group = self._build_analysis_group() + if analysis_group: + scroll_layout.addWidget(analysis_group) design_group = QGroupBox("Design") design_group.setStyleSheet( @@ -301,209 +226,10 @@ def init_ui(self): design_layout.setContentsMargins(10, 8, 10, 10) design_layout.setSpacing(8) - # === Superstructure Section (Contains Everything) === - structure_group = QGroupBox() - structure_group.setStyleSheet(""" - QGroupBox { - border: 1px solid #90AF13; - border-radius: 5px; - margin-top: 0px; - padding-top: 5px; - background-color: white; - } - """) - structure_layout = QVBoxLayout() - structure_layout.setContentsMargins(10, 10, 10, 10) - structure_layout.setSpacing(10) - - # Header with title and collapse icon - struct_header = QHBoxLayout() - struct_title = QLabel("Superstructure") - struct_title.setStyleSheet("font-size: 13px; font-weight: bold; color: #333;") - struct_header.addWidget(struct_title) - struct_header.addStretch() - - # Collapse/expand toggle using SVG icon - super_toggle_btn = QPushButton() - super_toggle_btn.setCursor(Qt.CursorShape.PointingHandCursor) - super_toggle_btn.setCheckable(True) - super_toggle_btn.setChecked(True) - super_toggle_btn.setIcon(QIcon(":/vectors/arrow_up_light.svg")) - super_toggle_btn.setIconSize(QSize(20, 20)) - super_toggle_btn.setStyleSheet(""" - QPushButton { - background: transparent; - border: none; - padding: 2px; - } - QPushButton:hover { - background: transparent; - } - QPushButton:pressed { - background: transparent; - } - """) - struct_header.addWidget(super_toggle_btn) - - structure_layout.addLayout(struct_header) - - # body widget that contains everything inside the Superstructure and can be hidden - super_body = QFrame() - super_body.setFrameShape(QFrame.NoFrame) - super_body_layout = QVBoxLayout(super_body) - super_body_layout.setContentsMargins(0, 0, 0, 0) - super_body_layout.setSpacing(10) - super_body.setVisible(True) - - # Additional Geometry (inside Superstructure body) - add_geo_row = QHBoxLayout() - add_geo_label = QLabel("Steel Design") - add_geo_label.setStyleSheet(""" - QLabel { - color: #000000; - font-size: 12px; - background: transparent; - } - """) - add_geo_label.setMinimumWidth(110) - add_geo_row.addWidget(add_geo_label) - - modify_geo_btn = QPushButton("Here") - modify_geo_btn.setCursor(Qt.CursorShape.PointingHandCursor) - modify_geo_btn.setStyleSheet(""" - QPushButton { - background-color: #90AF13; - color: white; - font-weight: bold; - border: none; - border-radius: 4px; - padding: 8px 20px; - font-size: 11px; - min-width: 80px; - } - QPushButton:hover { - background-color: #7a9a12; - } - """) - modify_geo_btn.clicked.connect(self.show_additional_inputs) - add_geo_row.addWidget(modify_geo_btn, 1) - super_body_layout.addLayout(add_geo_row) - - #--------------------------------------------- - - # Additional Geometry (inside Superstructure body) - add_geo_row = QHBoxLayout() - add_geo_label = QLabel("Deck Design") - add_geo_label.setStyleSheet(""" - QLabel { - color: #000000; - font-size: 12px; - background: transparent; - } - """) - add_geo_label.setMinimumWidth(110) - add_geo_row.addWidget(add_geo_label) - - modify_geo_btn = QPushButton("Here") - modify_geo_btn.setCursor(Qt.CursorShape.PointingHandCursor) - modify_geo_btn.setStyleSheet(""" - QPushButton { - background-color: #90AF13; - color: white; - font-weight: bold; - border: none; - border-radius: 4px; - padding: 8px 20px; - font-size: 11px; - min-width: 80px; - } - QPushButton:hover { - background-color: #7a9a12; - } - """) - modify_geo_btn.clicked.connect(self.show_additional_inputs) - add_geo_row.addWidget(modify_geo_btn, 1) - super_body_layout.addLayout(add_geo_row) - - - # Add body to structure layout - structure_layout.addWidget(super_body) - - def _toggle_superstructure(checked, body=super_body, btn=super_toggle_btn): - # checked True means show body (open) - body.setVisible(checked) - btn.setIcon(QIcon(":/vectors/arrow_up_light.svg" if checked else ":/vectors/arrow_down_light.svg")) - - super_toggle_btn.toggled.connect(_toggle_superstructure) - structure_group.setLayout(structure_layout) - design_layout.addWidget(structure_group) - - # === Substructure Section (Contains Everything) === - structure_group = QGroupBox() - structure_group.setStyleSheet(""" - QGroupBox { - border: 1px solid #90AF13; - border-radius: 5px; - margin-top: 0px; - padding-top: 5px; - background-color: white; - } - """) - structure_layout = QVBoxLayout() - structure_layout.setContentsMargins(10, 10, 10, 10) - structure_layout.setSpacing(10) - - # Header with title and collapse icon - struct_header = QHBoxLayout() - struct_title = QLabel("Substructure") - struct_title.setStyleSheet("font-size: 13px; font-weight: bold; color: #333;") - struct_header.addWidget(struct_title) - struct_header.addStretch() - - # Collapse/expand toggle using SVG icon - toggle_btn = QPushButton() - toggle_btn.setCursor(Qt.CursorShape.PointingHandCursor) - toggle_btn.setCheckable(True) - toggle_btn.setChecked(True) - toggle_btn.setIcon(QIcon(":/vectors/arrow_up_light.svg")) - toggle_btn.setIconSize(QSize(20, 20)) - toggle_btn.setStyleSheet(""" - QPushButton { - background: transparent; - border: none; - padding: 2px; - } - QPushButton:hover { - background: transparent; - } - QPushButton:pressed { - background: transparent; - } - """) - struct_header.addWidget(toggle_btn) - - structure_layout.addLayout(struct_header) - - # body widget that contains everything inside the Superstructure and can be hidden - structure_body = QFrame() - structure_body.setFrameShape(QFrame.NoFrame) - structure_body_layout = QVBoxLayout(structure_body) - structure_body_layout.setContentsMargins(0, 0, 0, 0) - structure_body_layout.setSpacing(10) - structure_body.setVisible(True) - - - # Add body to structure layout - structure_layout.addWidget(structure_body) - - def _toggle_structure(checked): - # checked True means show body (open) - structure_body.setVisible(checked) - toggle_btn.setIcon(QIcon(":/vectors/arrow_up_light.svg" if checked else ":/vectors/arrow_down_light.svg")) - - toggle_btn.toggled.connect(_toggle_structure) - structure_group.setLayout(structure_layout) - design_layout.addWidget(structure_group) + # Dynamic design sections + for section_cfg in self.section_configs: + section_group = self._create_toggle_group(section_cfg) + design_layout.addWidget(section_group) scroll_layout.addWidget(design_group) scroll_layout.addStretch() @@ -531,4 +257,322 @@ def _toggle_structure(checked): def show_additional_inputs(self): """Handle showing additional geometry inputs.""" # Implement your logic here - print("Show additional inputs clicked") \ No newline at end of file + print("Show additional inputs clicked") + + # --- Helpers for dynamic section/button rendering --- + def _load_configs(self): + if self.backend and hasattr(self.backend, "output_values"): + try: + cfg = self.backend.output_values(flag=None) + if cfg is not None: + return self._normalize_section_configs(cfg) + except Exception: + pass + return {"analysis": None, "design": []} + + def _normalize_section_configs(self, cfg): + result = {"analysis": None, "design": []} + if not cfg: + return result + + # Already structured dict + if isinstance(cfg, dict): + result["analysis"] = cfg.get("analysis") + if isinstance(cfg.get("design"), list): + result["design"] = cfg.get("design") + return result + + # Legacy dict list: treat as design-only + if isinstance(cfg, list) and all(isinstance(item, dict) for item in cfg): + result["design"] = cfg + return result + + # Tuple-based definitions similar to input_values + if isinstance(cfg, list) and all(isinstance(item, tuple) for item in cfg): + for item in cfg: + if len(item) < 7: + continue + _, display_name, ui_type, _, is_visible, _, metadata = item + if ui_type != TYPE_TITLE or not is_visible: + continue + metadata = metadata or {} + kind = metadata.get("kind", "design") + if kind == "analysis": + result["analysis"] = { + "title": display_name, + "fields": metadata.get("fields", []), + } + continue + rows = metadata.get("rows") or metadata.get("post_rows") or [] + if not isinstance(rows, list): + rows = [] + result["design"].append({"title": display_name, "rows": rows}) + return result + + return result + + def _default_analysis_config(self): + return { + "title": "Analysis Results", + "fields": [ + {"type": "combobox", "label": "Member:", "values": ["All"]}, + { + "type": "combobox", + "label": "Load Combination:", + "values": ["Envelope"], + }, + { + "type": "checkbox_grid", + "columns": [["Fx", "Mx", "Dx"], ["Fy", "My", "Dy"], ["Fz", "Mz", "Dz"]], + }, + { + "type": "checkbox_row", + "label": "Display Options:", + "options": ["Max", "Min"], + }, + {"type": "checkbox", "label": "Controlling Utilization Ratio"}, + ], + } + + def _analysis_group_style(self): + return ( + "QGroupBox {\n" + " font-weight: bold;\n" + " font-size: 11px;\n" + " color: #333;\n" + " border: 1px solid #90AF13;\n" + " border-radius: 4px;\n" + " margin-top: 8px;\n" + " padding-top: 12px;\n" + " background-color: white;\n" + "}\n" + "QGroupBox::title {\n" + " subcontrol-origin: margin;\n" + " subcontrol-position: top left;\n" + " left: 8px;\n" + " padding: 0 4px;\n" + " background-color: white;\n" + "}" + ) + + def _build_analysis_group(self): + cfg = self.analysis_config or self._default_analysis_config() + if not cfg: + return None + + group = QGroupBox(cfg.get("title", "Analysis Results")) + group.setStyleSheet(self._analysis_group_style()) + layout = QVBoxLayout(group) + layout.setContentsMargins(10, 8, 10, 10) + layout.setSpacing(8) + + for field_cfg in cfg.get("fields", []): + self._add_analysis_field(layout, field_cfg) + + return group + + def _normalize_field_cfg(self, field_cfg): + if isinstance(field_cfg, dict): + return field_cfg + if isinstance(field_cfg, tuple) and len(field_cfg) >= 7: + key, label, field_type, values, is_visible, _validator, metadata = field_cfg + if not is_visible: + return None + meta = metadata or {} + normalized = { + "key": key, + "label": label, + "type": field_type, + "values": values, + } + normalized.update(meta) + return normalized + return None + + def _add_analysis_field(self, layout, field_cfg): + cfg = self._normalize_field_cfg(field_cfg) + if not cfg: + return + field_type = cfg.get("type") + if field_type == "combobox": + row = QHBoxLayout() + row.setContentsMargins(0, 0, 0, 0) + row.setSpacing(8) + label = QLabel(cfg.get("label", "")) + label.setStyleSheet("font-size: 10px; color: #333; font-weight: normal;") + label.setMinimumWidth(cfg.get("label_min_width", 100)) + combo = NoScrollComboBox() + values = cfg.get("values") or [] + combo.addItems(values) + default = cfg.get("default") + if default and default in values: + combo.setCurrentText(default) + apply_field_style(combo) + row.addWidget(label) + row.addWidget(combo) + layout.addLayout(row) + elif field_type == "checkbox_grid": + columns = cfg.get("columns") or cfg.get("values") or [] + grid = QHBoxLayout() + grid.setContentsMargins(0, 0, 0, 0) + grid.setSpacing(8) + for col_items in columns: + col_layout = QVBoxLayout() + col_layout.setContentsMargins(0, 0, 0, 0) + col_layout.setSpacing(2) + for text in col_items or []: + cb = QCheckBox(str(text)) + col_layout.addWidget(cb) + grid.addLayout(col_layout) + if cfg.get("add_stretch", True): + grid.addStretch() + layout.addLayout(grid) + elif field_type == "checkbox_row": + row = QHBoxLayout() + row.setContentsMargins(0, 0, 0, 0) + row.setSpacing(12) + label = cfg.get("label") + if label: + lbl = QLabel(label) + lbl.setStyleSheet("font-size: 10px; color: #333; font-weight: normal; margin-top: 4px;") + row.addWidget(lbl) + options = cfg.get("options") or cfg.get("values") or [] + for text in options: + cb = QCheckBox(str(text)) + row.addWidget(cb) + if cfg.get("add_stretch", True): + row.addStretch() + layout.addLayout(row) + elif field_type == "checkbox": + cb = QCheckBox(cfg.get("label", "")) + layout.addWidget(cb) + + def _section_label_style(self): + return ( + "QLabel {\n" + " color: #000000;\n" + " font-size: 12px;\n" + " background: transparent;\n" + "}" + ) + + def _default_action_button_style(self): + return ( + "QPushButton {\n" + " background-color: #90AF13;\n" + " color: white;\n" + " font-weight: bold;\n" + " border: none;\n" + " border-radius: 4px;\n" + " padding: 8px 20px;\n" + " font-size: 11px;\n" + " min-width: 80px;\n" + "}\n" + "QPushButton:hover {\n" + " background-color: #7a9a12;\n" + "}\n" + ) + + def _create_action_button(self, cfg): + btn = QPushButton(cfg.get("text", "Action")) + btn.setCursor(Qt.CursorShape.PointingHandCursor) + btn.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) + style = cfg.get("style") or self._default_action_button_style() + btn.setStyleSheet(style) + if cfg.get("icon"): + btn.setIcon(QIcon(cfg["icon"])) + icon_size = cfg.get("icon_size") + if isinstance(icon_size, (list, tuple)) and len(icon_size) == 2: + btn.setIconSize(QSize(icon_size[0], icon_size[1])) + cb_name = cfg.get("action") + cb = getattr(self, cb_name, None) if cb_name else None + if callable(cb): + btn.clicked.connect(cb) + else: + btn.setEnabled(False) + return btn + + def _add_button_row(self, parent_layout, row_cfg): + row = QHBoxLayout() + row.setContentsMargins(0, 0, 0, 0) + row.setSpacing(8) + + label_text = row_cfg.get("label") + if label_text: + label = QLabel(label_text) + label.setStyleSheet(self._section_label_style()) + label.setMinimumWidth(row_cfg.get("label_min_width", 110)) + row.addWidget(label) + + buttons = row_cfg.get("buttons", []) + for cfg in buttons: + btn = self._create_action_button(cfg) + row.addWidget(btn, cfg.get("stretch", 1 if len(buttons) == 1 else 0)) + + if row_cfg.get("add_stretch", True): + row.addStretch() + + parent_layout.addLayout(row) + + def _create_toggle_group(self, section_cfg): + group = QGroupBox() + group.setStyleSheet( + "QGroupBox {\n" + " border: 1px solid #90AF13;\n" + " border-radius: 5px;\n" + " margin-top: 0px;\n" + " padding-top: 5px;\n" + " background-color: white;\n" + "}" + ) + layout = QVBoxLayout() + layout.setContentsMargins(10, 10, 10, 10) + layout.setSpacing(10) + + header = QHBoxLayout() + title = QLabel(section_cfg.get("title", "")) + title.setStyleSheet("font-size: 13px; font-weight: bold; color: #333;") + header.addWidget(title) + header.addStretch() + + toggle_btn = QPushButton() + toggle_btn.setCursor(Qt.CursorShape.PointingHandCursor) + toggle_btn.setCheckable(True) + toggle_btn.setChecked(True) + toggle_btn.setIcon(QIcon(":/vectors/arrow_up_light.svg")) + toggle_btn.setIconSize(QSize(20, 20)) + toggle_btn.setStyleSheet( + "QPushButton {\n" + " background: transparent;\n" + " border: none;\n" + " padding: 2px;\n" + "}\n" + "QPushButton:hover {\n" + " background: transparent;\n" + "}\n" + "QPushButton:pressed {\n" + " background: transparent;\n" + "}" + ) + header.addWidget(toggle_btn) + layout.addLayout(header) + + body = QFrame() + body.setFrameShape(QFrame.NoFrame) + body_layout = QVBoxLayout(body) + body_layout.setContentsMargins(0, 0, 0, 0) + body_layout.setSpacing(10) + body.setVisible(True) + + for row_cfg in section_cfg.get("rows", []): + self._add_button_row(body_layout, row_cfg) + + layout.addWidget(body) + + def _toggle(checked): + body.setVisible(checked) + toggle_btn.setIcon(QIcon(":/vectors/arrow_up_light.svg" if checked else ":/vectors/arrow_down_light.svg")) + + toggle_btn.toggled.connect(_toggle) + group.setLayout(layout) + return group \ No newline at end of file diff --git a/src/osdagbridge/desktop/ui/template_page.py b/src/osdagbridge/desktop/ui/template_page.py index c0a552e9..1cf942fc 100644 --- a/src/osdagbridge/desktop/ui/template_page.py +++ b/src/osdagbridge/desktop/ui/template_page.py @@ -188,7 +188,7 @@ def mousePressEvent(self, event): self.splitter.addWidget(central_widget) # root is the greatest level of parent that is the MainWindow - self.output_dock = OutputDock(parent=self) + self.output_dock = OutputDock(backend=self.backend, parent=self) self.splitter.addWidget(self.output_dock) # self.output_dock.setStyleSheet(self.output_dock.styleSheet()) self.output_dock.hide() diff --git a/src/osdagbridge/desktop/ui/utils/rolled_section_preview.py b/src/osdagbridge/desktop/ui/utils/rolled_section_preview.py index b933c30a..1a0cc527 100644 --- a/src/osdagbridge/desktop/ui/utils/rolled_section_preview.py +++ b/src/osdagbridge/desktop/ui/utils/rolled_section_preview.py @@ -6,7 +6,7 @@ from typing import Dict, Optional from PySide6.QtCore import QPointF, QRectF, Qt -from PySide6.QtGui import QColor, QFont, QFontMetricsF, QPainter, QPaintEvent, QPainterPath, QPen, QTextDocument +from PySide6.QtGui import QColor, QFont, QPainter, QPaintEvent, QPainterPath, QPen, QTextDocument from PySide6.QtWidgets import QSizePolicy, QWidget from osdagbridge.core.bridge_components.super_structure.girder.properties import BeamSection @@ -17,7 +17,7 @@ class RolledSectionPreview(QWidget): - """Render a rolled section with CAD-style dimension annotations.""" + """Render a rolled or welded section with CAD-style dimension annotations.""" def __init__(self, parent: Optional[QWidget] = None) -> None: super().__init__(parent) @@ -44,11 +44,9 @@ def __init__(self, parent: Optional[QWidget] = None) -> None: self._arrow_size = 9 # Minimum radii keep rolled sections visibly curved even when the - # catalogue omits R1/R2; welded previews stay sharp by skipping these. - self._min_root_radius_px = 6.0 + # catalogue omits R1/R2. + self._min_root_radius_px = 8.0 self._min_toe_radius_px = 4.0 - self._root_radius_ratio = 0.45 - self._toe_radius_ratio = 0.35 self.setMinimumSize(360, 260) self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) @@ -57,7 +55,7 @@ def __init__(self, parent: Optional[QWidget] = None) -> None: # Public API # ------------------------------------------------------------------ def set_section(self, section: Optional[BeamSection]) -> None: - """Update the preview to show the supplied ``BeamSection``.""" + """Update the preview to show the supplied ``BeamSection`` (assumed rolled).""" self._section = section if section is None: @@ -73,6 +71,7 @@ def set_section(self, section: Optional[BeamSection]) -> None: "root_radius_mm": float(section.root_radius_r1_mm or 0.0), "toe_radius_mm": float(section.root_radius_r2_mm or 0.0), } + # Rolled sections do not show weld symbols. self._show_welds = False self.update() @@ -87,7 +86,7 @@ def set_dimensions( bottom_flange_thickness_mm: Optional[float] = None, show_welds: bool = False, ) -> None: - """Feed custom dimensions (e.g., welded sections) directly.""" + """Feed custom dimensions directly (e.g., for welded sections).""" self._section = None self._dimensions = { @@ -97,6 +96,7 @@ def set_dimensions( "web_thickness": float(web_thickness_mm), "top_flange_thickness": float(flange_thickness_mm), "bottom_flange_thickness": float(bottom_flange_thickness_mm or flange_thickness_mm), + # Welded sections have no root/toe radii. "root_radius_mm": 0.0, "toe_radius_mm": 0.0, } @@ -176,8 +176,9 @@ def paintEvent(self, event: QPaintEvent) -> None: # noqa: D401 - Qt override bottom_height, ) - root_radius_px = max(0.0, float(dims.get("root_radius_mm", 0.0)) * scale) - toe_radius_px = max(0.0, float(dims.get("toe_radius_mm", 0.0)) * scale) + root_radius_px = float(dims.get("root_radius_mm", 0.0)) * scale + toe_radius_px = float(dims.get("toe_radius_mm", 0.0)) * scale + section_path = self._build_section_path( top_flange, web, @@ -324,6 +325,11 @@ def _build_section_path( root_radius: float, toe_radius: float, ) -> Optional[QPainterPath]: + """ + Builds the section path. + - For rolled sections: SHARP outer corners, CURVED inner corners/roots. + - For welded sections: ALL corners are SHARP (radii are 0). + """ if min(top_flange.width(), bottom_flange.width(), web.width()) <= 0: return None @@ -333,78 +339,112 @@ def _build_section_path( bf_top, bf_bottom = bottom_flange.top(), bottom_flange.bottom() web_left, web_right = web.left(), web.right() - top_toe = self._effective_toe_radius_px(toe_radius, top_flange) - bottom_toe = self._effective_toe_radius_px(toe_radius, bottom_flange) + # Determine effective radii. + # For welded sections, these will be 0.0. + # For rolled sections, minimum visual values are enforced if DB values are missing. top_root = self._effective_root_radius_px(root_radius, top_flange, web) bottom_root = self._effective_root_radius_px(root_radius, bottom_flange, web) + top_toe = self._effective_toe_radius_px(toe_radius, top_flange) + bottom_toe = self._effective_toe_radius_px(toe_radius, bottom_flange) path = QPainterPath() - path.moveTo(tf_left + top_toe, tf_top) - path.lineTo(tf_right - top_toe, tf_top) + + # --- TOP FLANGE --- + + # 1. Start at Top-Left Corner (Sharp) + path.moveTo(tf_left, tf_top) + + # 2. Top Edge -> Top-Right Corner (Sharp) + path.lineTo(tf_right, tf_top) + + # 3. Top-Right Vertical Face -> Start of Toe Curve + path.lineTo(tf_right, tf_bottom - top_toe) + + # 4. Top-Right Inner Toe Curve (R2) if top_toe > 0: - rect = QRectF(tf_right - 2 * top_toe, tf_top, 2 * top_toe, 2 * top_toe) - path.arcTo(rect, 90, -90) + rect = QRectF(tf_right - 2*top_toe, tf_bottom - 2*top_toe, 2*top_toe, 2*top_toe) + path.arcTo(rect, 0, -90) else: - path.lineTo(tf_right, tf_top) + path.lineTo(tf_right, tf_bottom) - path.lineTo(tf_right, tf_bottom) + # 5. Underside -> Start of Root Curve (R1) + path.lineTo(web_right + top_root, tf_bottom) + # 6. Top-Right Root Fillet (R1) if top_root > 0: - path.lineTo(web_right + top_root, tf_bottom) - # Root fillet: curve DOWN onto the web (adding material in the corner) - rect = QRectF(web_right, tf_bottom, 2 * top_root, 2 * top_root) + rect = QRectF(web_right, tf_bottom, 2*top_root, 2*top_root) path.arcTo(rect, 90, 90) else: path.lineTo(web_right, tf_bottom) + # 7. Web Right Side -> Bottom Root path.lineTo(web_right, bf_top - bottom_root) + + # 8. Bottom-Right Root Fillet (R1) if bottom_root > 0: - # Root fillet: curve OUT onto the bottom flange - rect = QRectF(web_right, bf_top - 2 * bottom_root, 2 * bottom_root, 2 * bottom_root) + rect = QRectF(web_right, bf_top - 2*bottom_root, 2*bottom_root, 2*bottom_root) path.arcTo(rect, 180, 90) else: path.lineTo(web_right, bf_top) - path.lineTo(bf_right, bf_top) - path.lineTo(bf_right, bf_bottom - bottom_toe) + # 9. Bottom Flange Top Side -> Inner Toe + path.lineTo(bf_right - bottom_toe, bf_top) + + # 10. Bottom-Right Inner Toe Curve (R2) if bottom_toe > 0: - rect = QRectF(bf_right - 2 * bottom_toe, bf_bottom - 2 * bottom_toe, 2 * bottom_toe, 2 * bottom_toe) - path.arcTo(rect, 0, -90) + rect = QRectF(bf_right - 2*bottom_toe, bf_top, 2*bottom_toe, 2*bottom_toe) + path.arcTo(rect, 90, -90) else: - path.lineTo(bf_right, bf_bottom) + path.lineTo(bf_right, bf_top) + + # 11. Bottom-Right Vertical Face -> Bottom-Right Corner (Sharp) + path.lineTo(bf_right, bf_bottom) + + # 12. Bottom Edge -> Bottom-Left Corner (Sharp) + path.lineTo(bf_left, bf_bottom) + + # 13. Bottom-Left Vertical Face -> Inner Toe + path.lineTo(bf_left, bf_top + bottom_toe) - path.lineTo(bf_left + bottom_toe, bf_bottom) + # 14. Bottom-Left Inner Toe Curve (R2) if bottom_toe > 0: - rect = QRectF(bf_left, bf_bottom - 2 * bottom_toe, 2 * bottom_toe, 2 * bottom_toe) - path.arcTo(rect, 270, -90) + rect = QRectF(bf_left, bf_top, 2*bottom_toe, 2*bottom_toe) + path.arcTo(rect, 180, -90) else: - path.lineTo(bf_left, bf_bottom) + path.lineTo(bf_left, bf_top) - path.lineTo(bf_left, bf_top) + # 15. Top Side -> Root + path.lineTo(web_left - bottom_root, bf_top) + # 16. Bottom-Left Root Fillet (R1) if bottom_root > 0: - path.lineTo(web_left - bottom_root, bf_top) - # Root fillet: curve UP onto the web - rect = QRectF(web_left - 2 * bottom_root, bf_top - 2 * bottom_root, 2 * bottom_root, 2 * bottom_root) + rect = QRectF(web_left - 2*bottom_root, bf_top - 2*bottom_root, 2*bottom_root, 2*bottom_root) path.arcTo(rect, 270, 90) else: path.lineTo(web_left, bf_top) + # 17. Web Left Side -> Top Root path.lineTo(web_left, tf_bottom + top_root) + + # 18. Top-Left Root Fillet (R1) if top_root > 0: - # Root fillet: curve OUT onto the top flange - rect = QRectF(web_left - 2 * top_root, tf_bottom, 2 * top_root, 2 * top_root) + rect = QRectF(web_left - 2*top_root, tf_bottom, 2*top_root, 2*top_root) path.arcTo(rect, 0, 90) else: path.lineTo(web_left, tf_bottom) - path.lineTo(tf_left, tf_bottom) - path.lineTo(tf_left, tf_top + top_toe) + # 19. Underside -> Inner Toe + path.lineTo(tf_left + top_toe, tf_bottom) + + # 20. Top-Left Inner Toe Curve (R2) if top_toe > 0: - rect = QRectF(tf_left, tf_top, 2 * top_toe, 2 * top_toe) - path.arcTo(rect, 180, -90) + rect = QRectF(tf_left, tf_bottom - 2*top_toe, 2*top_toe, 2*top_toe) + path.arcTo(rect, 270, -90) else: - path.lineTo(tf_left, tf_top) + path.lineTo(tf_left, tf_bottom) + + # 21. Left Face -> Back to Start (Sharp) + path.lineTo(tf_left, tf_top) path.closeSubpath() return path @@ -417,29 +457,32 @@ def _snap_coordinate(self, value: float, *, precision: float = 0.5) -> float: return round(value / precision) * precision def _effective_toe_radius_px(self, requested: float, flange: QRectF) -> float: - max_radius = max(0.0, min(flange.width(), flange.height()) / 2.0) - if max_radius == 0.0: + # If this is a welded section, radii must be sharp. + if self._show_welds: return 0.0 - radius = max(0.0, requested) - if radius <= 0.0 and not self._show_welds: - radius = max(max_radius * self._toe_radius_ratio, self._min_toe_radius_px) + max_radius = max(0.0, min(flange.width() / 2.0, flange.height())) + if max_radius == 0.0: + return 0.0 - return self._snap_coordinate(min(radius, max_radius)) + # Enforce Minimum Visual Radius (e.g. 4px) if requested is 0/missing for rolled sections + val = max(requested, self._min_toe_radius_px) + return self._snap_coordinate(min(val, max_radius)) def _effective_root_radius_px(self, requested: float, flange: QRectF, web: QRectF) -> float: - # Limit root radius to fit within the flange overhang and not exceed half web height + # If this is a welded section, radii must be sharp. + if self._show_welds: + return 0.0 + flange_overhang = (flange.width() - web.width()) / 2.0 max_radius = max(0.0, min(flange_overhang, web.height() / 2.0)) if max_radius == 0.0: return 0.0 - radius = max(0.0, requested) - if radius <= 0.0 and not self._show_welds: - radius = max(max_radius * self._root_radius_ratio, self._min_root_radius_px) - - return self._snap_coordinate(min(radius, max_radius)) + # Enforce Minimum Visual Radius (e.g. 8px) if requested is 0/missing for rolled sections + val = max(requested, self._min_root_radius_px) + return self._snap_coordinate(min(val, max_radius)) def _draw_welds(self, painter: QPainter, top_flange: QRectF, web: QRectF, bottom_flange: QRectF) -> None: painter.save() @@ -510,7 +553,7 @@ def _draw_placeholder(self, painter: QPainter) -> None: font.setFamily(self._brand_font_family) font.setPointSizeF(max(font.pointSizeF(), 10.0)) painter.setFont(font) - painter.drawText(self.rect(), Qt.AlignCenter, "Select a rolled section to preview") + painter.drawText(self.rect(), Qt.AlignCenter, "Select a section to preview") painter.restore() def _set_dimension_pen(self, painter: QPainter, key: str) -> QColor: @@ -698,4 +741,4 @@ def _format_mm(value: float) -> str: rounded = round(value) if abs(value - rounded) < 0.01: return f"{rounded} mm" - return f"{value:.1f} mm" + return f"{value:.1f} mm" \ No newline at end of file From aaa93c518ccbdf610616048487b6247fe0369c2a Mon Sep 17 00:00:00 2001 From: Sreejesh06 Date: Thu, 8 Jan 2026 12:24:05 +0530 Subject: [PATCH 57/59] Enhance arrow geometry in RolledSectionPreview to 1/3 ratio --- .../ui/utils/rolled_section_preview.py | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/osdagbridge/desktop/ui/utils/rolled_section_preview.py b/src/osdagbridge/desktop/ui/utils/rolled_section_preview.py index 1a0cc527..b580abaa 100644 --- a/src/osdagbridge/desktop/ui/utils/rolled_section_preview.py +++ b/src/osdagbridge/desktop/ui/utils/rolled_section_preview.py @@ -653,18 +653,30 @@ def _draw_arrow_head(self, painter: QPainter, tip: QPointF, direction: QPointF, length = math.hypot(direction.x(), direction.y()) if length == 0: return + unit = QPointF(direction.x() / length, direction.y() / length) normal = QPointF(-unit.y(), unit.x()) + arrow = self._arrow_size - base = tip + unit * (arrow * 0.9) - left = base + normal * (arrow * 0.45) - right = base - normal * (arrow * 0.45) + + # --- Adjusted Geometry for Narrow/Tall Arrows --- + # 1.3 makes the arrow longer/taller (previously 0.9) + arrow_length = arrow * 1.3 + + # 0.25 makes the base narrower (previously 0.45) + # This creates a sharp, technical drafting look. + arrow_half_width = arrow * 0.25 + + base = tip + unit * arrow_length + left = base + normal * arrow_half_width + right = base - normal * arrow_half_width + painter.save() painter.setBrush(color) painter.setPen(Qt.NoPen) painter.drawPolygon([tip, left, right]) painter.restore() - + def _draw_label( self, painter: QPainter, From a4f3e45d449cd8eb50caebb84a9c2f42f92d9dab Mon Sep 17 00:00:00 2001 From: Sreejesh06 Date: Sat, 10 Jan 2026 01:12:50 +0530 Subject: [PATCH 58/59] refactored girder details and section inputs of member properties --- .../desktop/ui/dialogs/additional_inputs.py | 470 ++++++++++++++++-- 1 file changed, 441 insertions(+), 29 deletions(-) diff --git a/src/osdagbridge/desktop/ui/dialogs/additional_inputs.py b/src/osdagbridge/desktop/ui/dialogs/additional_inputs.py index b55ab130..8f15059f 100644 --- a/src/osdagbridge/desktop/ui/dialogs/additional_inputs.py +++ b/src/osdagbridge/desktop/ui/dialogs/additional_inputs.py @@ -12,7 +12,7 @@ QTextEdit, QDialog, QSizePolicy, QSizeGrip ) from PySide6.QtCore import Qt, Signal, QSize -from PySide6.QtGui import QDoubleValidator, QIntValidator +from PySide6.QtGui import QDoubleValidator, QIntValidator, QStandardItemModel from osdagbridge.core.bridge_components.super_structure.girder import properties as girder_properties from osdagbridge.core.utils.common import * @@ -178,6 +178,90 @@ def create_action_button_bar(parent=None): } """ + +class CheckableComboBox(QComboBox): + """Multi-select combo box that keeps track of checked girders.""" + + checkedItemsChanged = Signal() + + def __init__(self, parent=None): + super().__init__(parent) + model = QStandardItemModel(self) + self.setModel(model) + self._updating_selection = False + model.itemChanged.connect(self._handle_item_changed) + + def addItem(self, text, userData=None): + super().addItem(text, userData) + self._initialize_item(self.count() - 1) + + def addItems(self, texts): + for text in texts: + self.addItem(text) + + def _initialize_item(self, index): + item = self.model().item(index) + if not item: + return + item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsUserCheckable) + if item.text().lower() == "all": + item.setData(Qt.Checked, Qt.CheckStateRole) + else: + item.setData(Qt.Unchecked, Qt.CheckStateRole) + + def _handle_item_changed(self, item): + if self._updating_selection or item is None: + return + self._updating_selection = True + try: + text = item.text().strip().lower() + if text == "all": + if item.checkState() == Qt.Checked: + self._uncheck_non_all_items() + else: + if item.checkState() == Qt.Checked: + self._uncheck_all_item() + finally: + self._updating_selection = False + self.checkedItemsChanged.emit() + + def _uncheck_non_all_items(self): + model = self.model() + for row in range(model.rowCount()): + item = model.item(row) + if not item: + continue + if item.text().strip().lower() == "all": + continue + item.setCheckState(Qt.Unchecked) + + def _uncheck_all_item(self): + model = self.model() + for row in range(model.rowCount()): + item = model.item(row) + if item and item.text().strip().lower() == "all": + item.setCheckState(Qt.Unchecked) + break + + def checked_items(self, include_all=False): + model = self.model() + selected = [] + all_checked = False + for row in range(model.rowCount()): + item = model.item(row) + if not item or item.checkState() != Qt.Checked: + continue + text = item.text() + if text.strip().lower() == "all": + all_checked = True + if include_all: + selected.append(text) + else: + selected.append(text) + if all_checked and not include_all: + return [] + return [text for text in selected if text.strip().lower() != "all"] + # ================================================================================= # MAIN IMPLEMENTATION # ================================================================================= @@ -302,6 +386,12 @@ def init_ui(self): main_layout.addSpacing(6) main_layout.addWidget(action_bar) + def accept(self): + girder_tab = getattr(getattr(self, "section_properties_tab", None), "girder_tab", None) + if girder_tab and not girder_tab.validate_member_properties(): + return + super().accept() + def _show_placeholder_message(self, action_name): """Show placeholder message for action buttons""" QMessageBox.information(self, action_name, "This action will be available in an upcoming update.") @@ -1289,6 +1379,8 @@ class SectionPropertiesTab(QWidget): def __init__(self, parent=None): super().__init__(parent) self.nav_buttons = [] + self.section_widgets = [] + self.girder_tab = None self.init_ui() def init_ui(self): @@ -1374,6 +1466,9 @@ def init_ui(self): nav_bar_layout.addWidget(btn) section_widget = widget_class() + if isinstance(section_widget, GirderDetailsTab): + self.girder_tab = section_widget + self.section_widgets.append(section_widget) self.stack.addWidget(section_widget) if self.nav_buttons: @@ -1394,7 +1489,11 @@ def __init__(self, parent=None): super().__init__(parent) self.welded_rows = [] self.rolled_rows = [] + self.symmetry_row = [] self.section_property_inputs = {} + self.segment_chain = {} + self._suppress_distance_updates = False + self.available_girders = [f"G{i}" for i in range(1, 6)] self.init_ui() def init_ui(self): @@ -1427,25 +1526,31 @@ def _build_overview_card(self): layout.setContentsMargins(18, 16, 18, 16) layout.setHorizontalSpacing(20) layout.setVerticalSpacing(12) + layout.setColumnStretch(1, 1) + layout.setColumnStretch(3, 1) - self.select_girder_combo = QComboBox() - self.select_girder_combo.addItems(["Girder 1", "Girder 2", "Girder 3", "Girder 4", "Girder 5", "All"]) + self.select_girder_combo = CheckableComboBox() + self.select_girder_combo.addItems(["All"] + self.available_girders) apply_field_style(self.select_girder_combo) self._set_field_width(self.select_girder_combo) + layout.addWidget(self._create_label("Select Girder:"), 0, 0, Qt.AlignLeft | Qt.AlignVCenter) + layout.addWidget(self.select_girder_combo, 0, 1, 1, 3) self.span_combo = QComboBox() - self.span_combo.addItems(["Custom", "Full Length"]) + self.span_combo.addItems(["Full Length", "Custom"]) apply_field_style(self.span_combo) self._set_field_width(self.span_combo) + self.span_combo.currentTextChanged.connect(self._on_span_changed) + layout.addWidget(self._create_label("Span:"), 1, 0, Qt.AlignLeft | Qt.AlignVCenter) + layout.addWidget(self.span_combo, 1, 1, Qt.AlignLeft) - self.member_id_input = QLineEdit("G1-1") + self.member_id_input = QLineEdit() + self.member_id_input.setPlaceholderText("G1-1") apply_field_style(self.member_id_input) self._set_field_width(self.member_id_input) - - self.member_select_combo = QComboBox() - self.member_select_combo.addItems(["Girder 1", "Girder 2", "Girder 3", "Girder 4", "Girder 5"]) - apply_field_style(self.member_select_combo) - self._set_field_width(self.member_select_combo) + self.member_id_input.textChanged.connect(self._on_member_id_changed) + layout.addWidget(self._create_label("Member ID:"), 1, 2, Qt.AlignLeft | Qt.AlignVCenter) + layout.addWidget(self.member_id_input, 1, 3, Qt.AlignLeft) self.distance_start_input = QLineEdit("0") self.distance_end_input = QLineEdit("30") @@ -1453,26 +1558,22 @@ def _build_overview_card(self): apply_field_style(self.distance_end_input) self._set_field_width(self.distance_start_input, 80) self._set_field_width(self.distance_end_input, 80) + self.distance_start_input.editingFinished.connect(self._on_distance_start_changed) + self.distance_end_input.editingFinished.connect(self._on_distance_end_changed) + distance_row = self._build_distance_row() + layout.addWidget(self._create_label("Distance from left edge (m):"), 2, 0, Qt.AlignLeft | Qt.AlignTop) + layout.addLayout(distance_row, 2, 1, Qt.AlignLeft) self.length_input = QLineEdit("30") apply_field_style(self.length_input) self._set_field_width(self.length_input) + self.length_input.setReadOnly(True) + self.length_input.textChanged.connect(self._on_length_changed) + layout.addWidget(self._create_label("Length (m):"), 2, 2, Qt.AlignLeft | Qt.AlignVCenter) + layout.addWidget(self.length_input, 2, 3, Qt.AlignLeft) - layout.addWidget(self._create_label("Select Girder:"), 0, 0) - layout.addWidget(self.select_girder_combo, 0, 1) - layout.addWidget(self._create_label("Span:"), 0, 2) - layout.addWidget(self.span_combo, 0, 3) - - layout.addWidget(self._create_label("Member ID:"), 1, 0) - layout.addWidget(self.member_id_input, 1, 1) - layout.addWidget(self._create_label("Length (m):"), 1, 2) - layout.addWidget(self.length_input, 1, 3) - - layout.addWidget(self._create_label("Distance from left edge (m):"), 2, 0) - layout.addLayout(self._build_distance_row(), 2, 1) - - layout.addWidget(self._create_label("Member:"), 2, 2) - layout.addWidget(self.member_select_combo, 2, 3) + self._setup_girder_selector() + self._on_span_changed(self.span_combo.currentText()) return card @@ -1518,7 +1619,7 @@ def _build_section_card(self): inputs_grid.setColumnStretch(1, 1) self.design_combo = QComboBox() - self.design_combo.addItems(["Customized", "Optimized"]) + self.design_combo.addItems(["Optimized", "Customized"]) apply_field_style(self.design_combo) row = self._add_box_row(inputs_grid, 0, "Design:", self.design_combo) @@ -1530,7 +1631,7 @@ def _build_section_card(self): self.symmetry_combo = QComboBox() self.symmetry_combo.addItems(["Girder Symmetric", "Girder Unsymmetric"]) apply_field_style(self.symmetry_combo) - row = self._add_box_row(inputs_grid, row, "Symmetry:", self.symmetry_combo) + row = self._add_box_row(inputs_grid, row, "Symmetry:", self.symmetry_combo, self.symmetry_row) self.total_depth_input = self._create_line_edit() row = self._add_box_row( @@ -1707,10 +1808,12 @@ def _build_section_card(self): main_layout.addWidget(right_column) + self.design_combo.currentTextChanged.connect(self._on_design_changed) self.type_combo.currentTextChanged.connect(self._on_type_changed) self.is_section_combo.currentTextChanged.connect(self._update_preview) for watcher in (self.total_depth_input, self.top_width_input, self.bottom_width_input): watcher.textChanged.connect(self._update_preview) + self._on_design_changed(self.design_combo.currentText()) self._on_type_changed(self.type_combo.currentText()) return container @@ -1752,11 +1855,320 @@ def _set_field_width(self, widget, width=230): widget.setMaximumWidth(width) widget.setMinimumWidth(min(width, 160)) + def _setup_girder_selector(self): + if hasattr(self.select_girder_combo, "checkedItemsChanged"): + self.select_girder_combo.checkedItemsChanged.connect(self._on_girders_selection_changed) + self._on_girders_selection_changed() + + def _on_girders_selection_changed(self, *args): + if self.span_combo.currentText() == "Full Length": + self._update_member_id_edit_state() + return + current_text = self.member_id_input.text().strip() + if not self._is_valid_segment_id(current_text): + default_id = self._default_member_segment_id() + self._set_member_id_text(default_id) + self._update_member_id_edit_state() + + def _get_selected_girders(self): + selected = [] + if hasattr(self.select_girder_combo, "checked_items"): + selected = self.select_girder_combo.checked_items(include_all=True) + model = self.select_girder_combo.model() + if model is None: + return self.available_girders.copy() + all_checked = False + if selected: + all_checked = any(item.strip().lower() == "all" for item in selected) + else: + for row in range(model.rowCount()): + item = model.item(row) + if not item or item.checkState() != Qt.Checked: + continue + text = item.text() + if text.strip().lower() == "all": + all_checked = True + else: + selected.append(text) + if all_checked or not selected: + return self.available_girders.copy() + normalized = [text for text in selected if text in self.available_girders] + return normalized if normalized else self.available_girders.copy() + + def _default_member_segment_id(self, girders=None): + girders = girders or self._get_selected_girders() + base = girders[0] if girders else "G1" + return f"{base}-1" + + def _set_member_id_text(self, value, block_signals=False): + if block_signals: + previous = self.member_id_input.blockSignals(True) + self.member_id_input.setText(value) + self.member_id_input.blockSignals(previous) + else: + self.member_id_input.setText(value) + + def _is_valid_segment_id(self, member_id): + if not member_id or "-" not in member_id: + return False + base, index = self._split_member_id(member_id) + return bool(base and isinstance(index, int)) + + def _update_member_id_edit_state(self): + is_full_span = self.span_combo.currentText() == "Full Length" + self.member_id_input.setReadOnly(is_full_span) + if is_full_span: + girders = self._get_selected_girders() + display = ", ".join(girders) if girders else "G1" + self._set_member_id_text(display, block_signals=True) + else: + current_text = self.member_id_input.text().strip() + if not self._is_valid_segment_id(current_text): + default_id = self._default_member_segment_id() + self._set_member_id_text(default_id) + self._update_distance_field_states() + + def _on_design_changed(self, text): + is_custom = text.lower() == "customized" + toggle_targets = ( + self.type_combo, + self.symmetry_combo, + self.total_depth_input, + self.top_width_input, + self.bottom_width_input, + ) + for widget in toggle_targets: + widget.setEnabled(is_custom) + self._apply_type_state() + def _on_type_changed(self, text): - is_welded = text.lower() == "welded" + self._apply_type_state() + self._update_preview() + + def _apply_type_state(self): + is_welded = self.type_combo.currentText().lower() == "welded" + is_custom = self.design_combo.currentText().lower() == "customized" + self._set_row_visibility(self.welded_rows, is_welded) self._set_row_visibility(self.rolled_rows, not is_welded) - self._update_preview() + + for label, widget in self.symmetry_row: + label.setVisible(is_welded) + widget.setVisible(is_welded) + self.symmetry_combo.setEnabled(is_welded and is_custom) + + plate_widgets = ( + self.total_depth_input, + self.web_thickness_combo, + self.top_width_input, + self.top_thickness_combo, + self.bottom_width_input, + self.bottom_thickness_combo, + ) + for widget in plate_widgets: + widget.setEnabled(is_welded and is_custom) + widget.setVisible(is_welded) + + self.is_section_combo.setVisible(not is_welded) + self.is_section_combo.setEnabled(not is_welded) + + def _update_distance_field_states(self): + member_id = self.member_id_input.text().strip() + is_full_span = self.span_combo.currentText() == "Full Length" + if is_full_span: + self.distance_start_input.setReadOnly(True) + self.distance_end_input.setReadOnly(True) + return + if not self._is_valid_segment_id(member_id): + self.distance_start_input.setReadOnly(True) + self.distance_end_input.setReadOnly(True) + return + self.distance_start_input.setReadOnly(self._is_first_segment(member_id)) + self.distance_end_input.setReadOnly(False) + + def _on_span_changed(self, span_text): + is_full = span_text == "Full Length" + self.length_input.setReadOnly(not is_full) + if is_full: + self._apply_full_length_distances() + else: + member_id = self.member_id_input.text().strip() + if not self._is_valid_segment_id(member_id): + member_id = self._default_member_segment_id() + self._set_member_id_text(member_id) + self._load_segment_distances(member_id) + self._update_member_id_edit_state() + + def _on_length_changed(self, _): + if self.span_combo.currentText() == "Full Length": + self._apply_full_length_distances() + + def _apply_full_length_distances(self): + self._suppress_distance_updates = True + try: + total_span = self._get_total_span() + self._set_line_edit_value(self.distance_start_input, 0.0) + self._set_line_edit_value(self.distance_end_input, total_span) + finally: + self._suppress_distance_updates = False + + def _on_member_id_changed(self, member_id): + member_id = member_id.strip() + if not member_id or self.span_combo.currentText() == "Full Length": + return + if not self._is_valid_segment_id(member_id): + self._update_distance_field_states() + return + if self._is_first_segment(member_id): + self._update_segment_record(member_id, start=0.0) + else: + previous_id = self._get_previous_segment_id(member_id) + previous_end = self.segment_chain.get(previous_id, {}).get("end") if previous_id else None + if previous_end is not None: + self._update_segment_record(member_id, start=previous_end) + self._load_segment_distances(member_id) + self._update_distance_field_states() + + def _on_distance_start_changed(self): + if self._suppress_distance_updates: + return + current_id = self.member_id_input.text().strip() + if not current_id or not self._is_valid_segment_id(current_id): + return + if self._is_first_segment(current_id): + self._suppress_distance_updates = True + try: + self._set_line_edit_value(self.distance_start_input, 0.0) + finally: + self._suppress_distance_updates = False + self._update_segment_record(current_id, start=0.0) + return + value = self._parse_float(self.distance_start_input.text()) or 0.0 + self._update_segment_record(current_id, start=value) + + def _on_distance_end_changed(self): + if self._suppress_distance_updates: + return + current_id = self.member_id_input.text().strip() + if not current_id or self.span_combo.currentText() == "Full Length" or not self._is_valid_segment_id(current_id): + return + end_value = self._parse_float(self.distance_end_input.text()) + if end_value is None: + end_value = 0.0 + + start_value = self._parse_float(self.distance_start_input.text()) + if start_value is None: + start_value = self.segment_chain.get(current_id, {}).get("start") + if start_value is None and self._is_first_segment(current_id): + start_value = 0.0 + + if start_value is not None: + self._update_segment_record(current_id, start=start_value) + self._update_segment_record(current_id, end=end_value) + self._propagate_next_segment_start(current_id, end_value) + + def _propagate_next_segment_start(self, member_id, next_start_value): + next_id = self._get_next_segment_id(member_id) + if not next_id: + return + self._update_segment_record(next_id, start=next_start_value) + if next_id == self.member_id_input.text().strip() and self.span_combo.currentText() != "Full Length": + self._load_segment_distances(next_id) + + def _load_segment_distances(self, member_id): + if not member_id or not self._is_valid_segment_id(member_id): + return + record = self.segment_chain.setdefault(member_id, {}) + if self._is_first_segment(member_id): + record.setdefault("start", 0.0) + elif "start" not in record: + previous_id = self._get_previous_segment_id(member_id) + if previous_id: + previous = self.segment_chain.get(previous_id, {}) + if "end" in previous: + record["start"] = previous["end"] + + self._suppress_distance_updates = True + try: + if "start" in record: + self._set_line_edit_value(self.distance_start_input, record["start"]) + else: + self.distance_start_input.clear() + if "end" in record: + self._set_line_edit_value(self.distance_end_input, record["end"]) + else: + self.distance_end_input.clear() + finally: + self._suppress_distance_updates = False + + def _update_segment_record(self, member_id, start=None, end=None): + if not member_id or not self._is_valid_segment_id(member_id): + return + record = self.segment_chain.setdefault(member_id, {}) + if start is not None: + record["start"] = start + if end is not None: + record["end"] = end + + def _get_total_span(self): + return self._parse_float(self.length_input.text()) or 0.0 + + def _is_first_segment(self, member_id): + _, index = self._split_member_id(member_id) + return index == 1 + + def _get_next_segment_id(self, member_id): + base, index = self._split_member_id(member_id) + if base is None or index is None: + return None + return f"{base}-{index + 1}" + + def _get_previous_segment_id(self, member_id): + base, index = self._split_member_id(member_id) + if base is None or index is None or index <= 1: + return None + return f"{base}-{index - 1}" + + def _split_member_id(self, member_id): + if "-" not in member_id: + return member_id, None + base, index = member_id.rsplit("-", 1) + try: + return base, int(index) + except ValueError: + return base, None + + def _set_line_edit_value(self, line_edit, value): + if value is None: + return + text = f"{value:.3f}".rstrip("0").rstrip(".") + if not text: + text = "0" + previous_state = line_edit.blockSignals(True) + line_edit.setText(text) + line_edit.blockSignals(previous_state) + + def validate_member_properties(self) -> bool: + if self.design_combo.currentText() != "Customized": + return True + required_fields = [ + (self.total_depth_input, "Total Depth (d, mm)"), + (self.top_width_input, "Width of Top Flange (t_fw, mm)"), + (self.bottom_width_input, "Width of Bottom Flange (b_fw, mm)"), + ] + missing = [] + for field, label in required_fields: + value = self._parse_float(field.text()) + if value is None or value <= 0: + missing.append(label) + if missing: + QMessageBox.critical( + self, + "Incomplete Girder Inputs", + f"Please provide valid values for: {', '.join(missing)}.", + ) + return False + return True def _create_inner_box(self): """Create a bordered box for grouped controls""" From 25b4930ee37aa14ca59ae8faf974bc6868838aa7 Mon Sep 17 00:00:00 2001 From: Sreejesh06 Date: Sat, 10 Jan 2026 16:29:26 +0530 Subject: [PATCH 59/59] Refactor girder properties completed --- src/osdagbridge/core/utils/common.py | 9 +- .../desktop/ui/dialogs/additional_inputs.py | 234 ++++++++++++++++-- 2 files changed, 222 insertions(+), 21 deletions(-) diff --git a/src/osdagbridge/core/utils/common.py b/src/osdagbridge/core/utils/common.py index 2f407e2a..d7c5bfc7 100644 --- a/src/osdagbridge/core/utils/common.py +++ b/src/osdagbridge/core/utils/common.py @@ -199,12 +199,15 @@ "IRC 5 - Flush Median", "Custom" ] -VALUES_GIRDER_TYPE = ["IS Standard Rolled Beam", "Plate Girder"] -VALUES_GIRDER_SYMMETRY = ["Symmetrical", "Unsymmetrical"] +VALUES_GIRDER_TYPE = ["Welded", "Rolled"] +VALUES_GIRDER_SYMMETRY = ["Girder Symmetric", "Girder Unsymmetric"] +VALUES_GIRDER_DESIGN_MODE = ["Optimized", "Customized"] +VALUES_GIRDER_SPAN_MODE = ["Full Length", "Custom"] +VALUES_PROFILE_SCOPE = ["All", "Custom"] VALUES_OPTIMIZATION_MODE = ["Optimized", "Customized", "All"] VALUES_TORSIONAL_RESTRAINT = ["Fully Restrained", "Partially Restrained - Support Connect", "Partially Restrained - Bearing Support"] VALUES_WARPING_RESTRAINT = ["Both Flange Restraint", "No Restraint"] -VALUES_WEB_TYPE = ["Thin Web", "Thick Web"] +VALUES_WEB_TYPE = ["Thin Web with ITS", "Thick Web with ITS"] VALUES_STIFFENER_DESIGN = ["Simple Post", "Tension Field"] VALUES_CROSS_BRACING_TYPE = ["K-bracing", "K-bracing with top bracket", "X-bracing", "X-bracing with bottom bracket", "X-bracing with top and bottom brackets"] VALUES_END_DIAPHRAGM_TYPE = ["Cross Bracing", "Rolled Beam", "Welded Beam"] diff --git a/src/osdagbridge/desktop/ui/dialogs/additional_inputs.py b/src/osdagbridge/desktop/ui/dialogs/additional_inputs.py index 8f15059f..a3a6f1e1 100644 --- a/src/osdagbridge/desktop/ui/dialogs/additional_inputs.py +++ b/src/osdagbridge/desktop/ui/dialogs/additional_inputs.py @@ -2,6 +2,7 @@ Additional Inputs Widget for Highway Bridge Design Provides detailed input fields for manual bridge parameter definition """ +import math import sys import os from PySide6.QtWidgets import ( @@ -100,6 +101,10 @@ def get_lineedit_style(): background: #f1f1f1; color: #666; } + QLineEdit:read-only{ + background: #f6f6f6; + color: #555555; + } QLineEdit:hover { border: 1px solid #5d5d5d; } @@ -1490,6 +1495,7 @@ def __init__(self, parent=None): self.welded_rows = [] self.rolled_rows = [] self.symmetry_row = [] + self.web_type_row = [] self.section_property_inputs = {} self.segment_chain = {} self._suppress_distance_updates = False @@ -1537,7 +1543,7 @@ def _build_overview_card(self): layout.addWidget(self.select_girder_combo, 0, 1, 1, 3) self.span_combo = QComboBox() - self.span_combo.addItems(["Full Length", "Custom"]) + self.span_combo.addItems(VALUES_GIRDER_SPAN_MODE) apply_field_style(self.span_combo) self._set_field_width(self.span_combo) self.span_combo.currentTextChanged.connect(self._on_span_changed) @@ -1580,11 +1586,20 @@ def _build_overview_card(self): def _build_distance_row(self): row = QHBoxLayout() row.setContentsMargins(0, 0, 0, 0) - row.setSpacing(8) - row.addWidget(self._create_small_label("Start")) - row.addWidget(self.distance_start_input) - row.addWidget(self._create_small_label("End")) - row.addWidget(self.distance_end_input) + row.setSpacing(16) + + def _build_column(line_edit, caption): + column = QVBoxLayout() + column.setContentsMargins(0, 0, 0, 0) + column.setSpacing(2) + column.addWidget(line_edit) + label = self._create_small_label(caption) + label.setAlignment(Qt.AlignCenter) + column.addWidget(label, alignment=Qt.AlignCenter) + return column + + row.addLayout(_build_column(self.distance_start_input, "Start")) + row.addLayout(_build_column(self.distance_end_input, "End")) return row def _build_section_card(self): @@ -1619,17 +1634,17 @@ def _build_section_card(self): inputs_grid.setColumnStretch(1, 1) self.design_combo = QComboBox() - self.design_combo.addItems(["Optimized", "Customized"]) + self.design_combo.addItems(VALUES_GIRDER_DESIGN_MODE) apply_field_style(self.design_combo) row = self._add_box_row(inputs_grid, 0, "Design:", self.design_combo) self.type_combo = QComboBox() - self.type_combo.addItems(["Welded", "Rolled"]) + self.type_combo.addItems(VALUES_GIRDER_TYPE) apply_field_style(self.type_combo) row = self._add_box_row(inputs_grid, row, "Type:", self.type_combo) self.symmetry_combo = QComboBox() - self.symmetry_combo.addItems(["Girder Symmetric", "Girder Unsymmetric"]) + self.symmetry_combo.addItems(VALUES_GIRDER_SYMMETRY) apply_field_style(self.symmetry_combo) row = self._add_box_row(inputs_grid, row, "Symmetry:", self.symmetry_combo, self.symmetry_row) @@ -1643,7 +1658,7 @@ def _build_section_card(self): ) self.web_thickness_combo = QComboBox() - self.web_thickness_combo.addItems(["All", "Custom"]) + self.web_thickness_combo.addItems(VALUES_PROFILE_SCOPE) apply_field_style(self.web_thickness_combo) row = self._add_box_row( inputs_grid, @@ -1663,7 +1678,7 @@ def _build_section_card(self): ) self.top_thickness_combo = QComboBox() - self.top_thickness_combo.addItems(["All", "Custom"]) + self.top_thickness_combo.addItems(VALUES_PROFILE_SCOPE) apply_field_style(self.top_thickness_combo) row = self._add_box_row( inputs_grid, @@ -1683,7 +1698,7 @@ def _build_section_card(self): ) self.bottom_thickness_combo = QComboBox() - self.bottom_thickness_combo.addItems(["All", "Custom"]) + self.bottom_thickness_combo.addItems(VALUES_PROFILE_SCOPE) apply_field_style(self.bottom_thickness_combo) row = self._add_box_row( inputs_grid, @@ -1704,8 +1719,8 @@ def _build_section_card(self): # Restraint/Web details box restraint_box = self._create_inner_box() restraint_layout = QVBoxLayout(restraint_box) - restraint_layout.setContentsMargins(12, 8, 12, 12) - restraint_layout.setSpacing(8) + restraint_layout.setContentsMargins(12, 6, 12, 10) + restraint_layout.setSpacing(6) restraint_title = self._create_label("Restraint & Web Details:") restraint_layout.addWidget(restraint_title) @@ -1719,22 +1734,21 @@ def _build_section_card(self): restraint_grid.setColumnStretch(1, 1) self.torsion_combo = QComboBox() - self.torsion_combo.addItems(VALUES_TORSIONAL_RESTRAINT) apply_field_style(self.torsion_combo) row = self._add_box_row(restraint_grid, 0, "Torsional Restraint:", self.torsion_combo) self.warping_combo = QComboBox() - self.warping_combo.addItems(VALUES_WARPING_RESTRAINT) apply_field_style(self.warping_combo) row = self._add_box_row(restraint_grid, row, "Warping Restraint:", self.warping_combo) self.web_type_combo = QComboBox() - self.web_type_combo.addItems(["Thin Web with ITS", "Thick Web"]) apply_field_style(self.web_type_combo) - self._add_box_row(restraint_grid, row, "Web Type*:", self.web_type_combo) + self._add_box_row(restraint_grid, row, "Web Type*:", self.web_type_combo, self.web_type_row) restraint_layout.addLayout(restraint_grid) + restraint_layout.addStretch(1) left_column_layout.addWidget(restraint_box) + self._configure_restraint_fields() main_layout.addWidget(left_column) @@ -1939,6 +1953,9 @@ def _on_design_changed(self, text): ) for widget in toggle_targets: widget.setEnabled(is_custom) + if not is_custom: + self._lock_type_to_welded() + self._reset_section_state() self._apply_type_state() def _on_type_changed(self, text): @@ -1969,9 +1986,28 @@ def _apply_type_state(self): widget.setEnabled(is_welded and is_custom) widget.setVisible(is_welded) + for label, widget in self.web_type_row: + label.setVisible(is_welded) + widget.setVisible(is_welded) + widget.setEnabled(is_welded and is_custom) + self.is_section_combo.setVisible(not is_welded) self.is_section_combo.setEnabled(not is_welded) + def _lock_type_to_welded(self): + welded_index = self.type_combo.findText("Welded", Qt.MatchFixedString) + if welded_index != -1 and self.type_combo.currentIndex() != welded_index: + previous = self.type_combo.blockSignals(True) + self.type_combo.setCurrentIndex(welded_index) + self.type_combo.blockSignals(previous) + + def _reset_section_state(self): + for widget in (self.total_depth_input, self.top_width_input, self.bottom_width_input): + previous = widget.blockSignals(True) + widget.clear() + widget.blockSignals(previous) + self._update_preview() + def _update_distance_field_states(self): member_id = self.member_id_input.text().strip() is_full_span = self.span_combo.currentText() == "Full Length" @@ -2242,6 +2278,27 @@ def _populate_rolled_section_combo(self): self.is_section_combo.clear() self.is_section_combo.addItems(designations) + def _configure_restraint_fields(self): + torsion_items = self._constant_items("VALUES_TORSIONAL_RESTRAINT") + warping_items = self._constant_items("VALUES_WARPING_RESTRAINT") + web_type_items = self._constant_items("VALUES_WEB_TYPE") + + self._reload_combo_items(self.torsion_combo, torsion_items) + self._reload_combo_items(self.warping_combo, warping_items) + self._reload_combo_items(self.web_type_combo, web_type_items) + + @staticmethod + def _reload_combo_items(combo, items): + block = combo.blockSignals(True) + combo.clear() + combo.addItems(items) + combo.setCurrentIndex(0 if items else -1) + combo.blockSignals(block) + + @staticmethod + def _constant_items(constant_name): + return list(globals().get(constant_name, [])) + def _update_preview(self): if not hasattr(self, "section_preview"): return @@ -2284,6 +2341,7 @@ def _update_preview(self): if hasattr(self, "preview_caption"): self.preview_caption.setText(caption) + self._update_section_properties() def _gather_welded_dimensions(self): depth = self._parse_float(self.total_depth_input.text()) @@ -2307,6 +2365,146 @@ def _gather_welded_dimensions(self): "bottom_flange_thickness_mm": flange_thickness, } + def _update_section_properties(self): + if not self.section_property_inputs: + return + values = None + if self.type_combo.currentText().lower() == "welded": + dims = self._gather_welded_dimensions() + if dims: + values = self._compute_welded_properties(dims) + else: + designation = self.is_section_combo.currentText() + values = self._fetch_rolled_properties(designation) + if values: + self._apply_section_properties(values) + else: + self._clear_section_properties() + + def _fetch_rolled_properties(self, designation): + if not designation: + return None + beam = girder_properties.get_beam_profile(designation) + if not beam: + return None + values = { + "Mass, M (Kg/m)": beam.mass_per_meter_kg, + "Sectional Area, a (cm2)": beam.area_cm2, + "2nd Moment of Area, Iz (cm4)": beam.moment_of_inertia_zz_cm4, + "2nd Moment of Area, Iy (cm4)": beam.moment_of_inertia_yy_cm4, + "Radius of Gyration, rz (cm)": beam.radius_of_gyration_z_cm, + "Radius of Gyration, ry (cm)": beam.radius_of_gyration_y_cm, + "Elastic Modulus, Zz (cm3)": beam.elastic_section_modulus_z_cm3, + "Elastic Modulus, Zy (cm3)": beam.elastic_section_modulus_y_cm3, + "Plastic Modulus, Zuz (cm3)": beam.plastic_section_modulus_z_cm3, + "Plastic Modulus, Zuy (cm3)": beam.plastic_section_modulus_y_cm3, + "Torsion Constant, It (cm4)": beam.torsion_constant_cm4, + "Warping Constant, Iw (cm6)": beam.warping_constant_cm6, + } + area = values.get("Sectional Area, a (cm2)") + iz = values.get("2nd Moment of Area, Iz (cm4)") + iy = values.get("2nd Moment of Area, Iy (cm4)") + if values.get("Radius of Gyration, rz (cm)") is None and area and iz: + values["Radius of Gyration, rz (cm)"] = math.sqrt(iz / area) + if values.get("Radius of Gyration, ry (cm)") is None and area and iy: + values["Radius of Gyration, ry (cm)"] = math.sqrt(iy / area) + return values + + def _compute_welded_properties(self, dims): + depth = dims["depth_mm"] + top_width = dims["top_flange_width_mm"] + bottom_width = dims["bottom_flange_width_mm"] + web_thickness = dims["web_thickness_mm"] + top_thickness = dims["top_flange_thickness_mm"] + bottom_thickness = dims["bottom_flange_thickness_mm"] + + h_web = max(depth - top_thickness - bottom_thickness, 1.0) + area_top = top_width * top_thickness + area_bottom = bottom_width * bottom_thickness + area_web = web_thickness * h_web + area_total_mm2 = area_top + area_bottom + area_web + area_cm2 = area_total_mm2 / 100.0 + mass_kg_per_m = (area_total_mm2 / 1_000_000.0) * 7850.0 + + iz_web = (web_thickness * h_web ** 3) / 12.0 + iz_top = (top_width * top_thickness ** 3) / 12.0 + iz_bottom = (bottom_width * bottom_thickness ** 3) / 12.0 + distance_top = h_web / 2.0 + top_thickness / 2.0 + distance_bottom = h_web / 2.0 + bottom_thickness / 2.0 + iz_top += area_top * distance_top ** 2 + iz_bottom += area_bottom * distance_bottom ** 2 + iz_cm4 = (iz_web + iz_top + iz_bottom) / 10000.0 + + iy_web = (h_web * web_thickness ** 3) / 12.0 + iy_top = (top_thickness * top_width ** 3) / 12.0 + iy_bottom = (bottom_thickness * bottom_width ** 3) / 12.0 + iy_cm4 = (iy_web + iy_top + iy_bottom) / 10000.0 + + rz_cm = math.sqrt(iz_cm4 / area_cm2) if area_cm2 > 0 else None + ry_cm = math.sqrt(iy_cm4 / area_cm2) if area_cm2 > 0 else None + + depth_cm = depth / 10.0 + width_cm = max(top_width, bottom_width) / 10.0 + zz_cm3 = iz_cm4 / (depth_cm / 2.0) if depth_cm > 0 else None + zy_cm3 = iy_cm4 / (width_cm / 2.0) if width_cm > 0 else None + + zpl_major = ( + area_top * distance_top + + area_bottom * distance_bottom + + (web_thickness * h_web ** 2) / 4.0 + ) / 1000.0 + zpl_minor = ( + (top_thickness * top_width ** 2) / 4.0 + + (bottom_thickness * bottom_width ** 2) / 4.0 + + (h_web * web_thickness ** 2) / 4.0 + ) / 1000.0 + + torsion_constant_cm4 = ( + (top_width * top_thickness ** 3) / 3.0 + + (bottom_width * bottom_thickness ** 3) / 3.0 + + (h_web * web_thickness ** 3) / 3.0 + ) / 10000.0 + + warping_constant_cm6 = ( + ((top_width * top_thickness ** 3) + (bottom_width * bottom_thickness ** 3)) * h_web ** 2 / 24.0 + ) / 1_000_000.0 + + return { + "Mass, M (Kg/m)": mass_kg_per_m, + "Sectional Area, a (cm2)": area_cm2, + "2nd Moment of Area, Iz (cm4)": iz_cm4, + "2nd Moment of Area, Iy (cm4)": iy_cm4, + "Radius of Gyration, rz (cm)": rz_cm, + "Radius of Gyration, ry (cm)": ry_cm, + "Elastic Modulus, Zz (cm3)": zz_cm3, + "Elastic Modulus, Zy (cm3)": zy_cm3, + "Plastic Modulus, Zuz (cm3)": zpl_major, + "Plastic Modulus, Zuy (cm3)": zpl_minor, + "Torsion Constant, It (cm4)": torsion_constant_cm4, + "Warping Constant, Iw (cm6)": warping_constant_cm6, + } + + def _apply_section_properties(self, values): + for label, widget in self.section_property_inputs.items(): + display = self._format_property_value(values.get(label)) + previous = widget.blockSignals(True) + widget.setText(display) + widget.blockSignals(previous) + + def _clear_section_properties(self): + for widget in self.section_property_inputs.values(): + previous = widget.blockSignals(True) + widget.clear() + widget.blockSignals(previous) + + @staticmethod + def _format_property_value(value): + if value is None: + return "" + if isinstance(value, (int, float)): + return f"{value:.2f}" + return str(value) + @staticmethod def _parse_float(text): try: