Scene Roadmap
This commit is contained in:
33
setup/generateTabletopPDF/A3_10Arucos_50mm_Seet223.json
Normal file
33
setup/generateTabletopPDF/A3_10Arucos_50mm_Seet223.json
Normal file
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"page_format": "A3",
|
||||
"orientation": "portrait",
|
||||
"page_size_mm": {
|
||||
"width": 297.0,
|
||||
"height": 420.0
|
||||
},
|
||||
"seed": 223,
|
||||
"num_arucos": 10,
|
||||
"aruco_size_mm": 50.0,
|
||||
"aruco_dictionary": "DICT_4X4_250",
|
||||
"aruco_start_id": 46,
|
||||
"page_border_margin_mm": 20.0,
|
||||
"forbidden_rectangle_mm": {
|
||||
"x": 143.5,
|
||||
"y": 205.0,
|
||||
"w": 10.0,
|
||||
"h": 10.0
|
||||
},
|
||||
"forbidden_rectangle_margin_mm": 30.0,
|
||||
"placements": [
|
||||
{"id": 46, "position": [96.26, 119.02, -27.3], "normal": [0, 0, 1], "spin": 90},
|
||||
{"id": 47, "position": [158.68, 110.29, -27.3], "normal": [0, 0, 1], "spin": 90},
|
||||
{"id": 48, "position": [-43.31, 5.63, -27.3], "normal": [0, 0, 1], "spin": 90},
|
||||
{"id": 49, "position": [63.52, -4.25, -27.3], "normal": [0, 0, 1], "spin": 90},
|
||||
{"id": 50, "position": [27.77, 149.72, -27.3], "normal": [0, 0, 1], "spin": 90},
|
||||
{"id": 51, "position": [-98.03, 38.72, -27.3], "normal": [0, 0, 1], "spin": 90},
|
||||
{"id": 52, "position": [-120.71, 148.79, -27.3], "normal": [0, 0, 1], "spin": 90},
|
||||
{"id": 53, "position": [72.76, 59.77, -27.3], "normal": [0, 0, 1], "spin": 90},
|
||||
{"id": 54, "position": [-156.67, 34.05, -27.3], "normal": [0, 0, 1], "spin": 90},
|
||||
{"id": 55, "position": [-110.82, 93.01, -27.3], "normal": [0, 0, 1], "spin": 90}
|
||||
]
|
||||
}
|
||||
74
setup/generateTabletopPDF/A3_10Arucos_50mm_Seet223.pdf
Normal file
74
setup/generateTabletopPDF/A3_10Arucos_50mm_Seet223.pdf
Normal file
@@ -0,0 +1,74 @@
|
||||
%PDF-1.3
|
||||
%“Œ‹ž ReportLab Generated PDF document http://www.reportlab.com
|
||||
1 0 obj
|
||||
<<
|
||||
/F1 2 0 R /F2 3 0 R
|
||||
>>
|
||||
endobj
|
||||
2 0 obj
|
||||
<<
|
||||
/BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font
|
||||
>>
|
||||
endobj
|
||||
3 0 obj
|
||||
<<
|
||||
/BaseFont /Times-Roman /Encoding /WinAnsiEncoding /Name /F2 /Subtype /Type1 /Type /Font
|
||||
>>
|
||||
endobj
|
||||
4 0 obj
|
||||
<<
|
||||
/Contents 8 0 R /MediaBox [ 0 0 841.8898 1190.551 ] /Parent 7 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
5 0 obj
|
||||
<<
|
||||
/PageMode /UseNone /Pages 7 0 R /Type /Catalog
|
||||
>>
|
||||
endobj
|
||||
6 0 obj
|
||||
<<
|
||||
/Author (OpenAI) /CreationDate (D:20260609100839+02'00') /Creator (ReportLab PDF Library - www.reportlab.com) /Keywords () /ModDate (D:20260609100839+02'00') /Producer (ReportLab PDF Library - www.reportlab.com)
|
||||
/Subject (A0 poster with random ArUco placement) /Title (A3_10Arucos_50mm_Seet223) /Trapped /False
|
||||
>>
|
||||
endobj
|
||||
7 0 obj
|
||||
<<
|
||||
/Count 1 /Kids [ 4 0 R ] /Type /Pages
|
||||
>>
|
||||
endobj
|
||||
8 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2256
|
||||
>>
|
||||
stream
|
||||
Gasb^c)r<L%#+HM.C+V0R+R8eZUFpRXJ"GN0h@+g"WLbi='*AObm`^/.uekT^TKc`,iq!cbphBh5QC]L;bGq3dW[or@S)WAM(@-Kpc8<BGdBd2p\,bJT:b>G9Ol;/QtIaGHgCFun\\R[^AH)LDaL5K<a5F@5#HBrhRP!S9Uqq+)0EU[0C8;F^@>8hc6K6Q-e17IqXj](D4N^H9n1io5//-oU((jsCT;e\51G!$RnTZ;p57s"r=ne@2]#of7ERf;T(gFg`c@q3Mg:nHfM!N+P>$DsH3q.!3+"eSYI0L5WMthC6fq'?W/5'@0@#\6[V+To(cr+Jm>A-jSP#LkC\&faD@kR&*@VQ3F(gseG:WuLoc.FaHNRCE#&%so`Eirncr^:LCP^ZGJV@\d\#ea#Yb#dWDS<i2QX1QY$At)ei4K'%ceHsl`k5ka%LB[e!*C=QKO&X3@6KC%I4bki`j$jaC=eVV8,6=O/>>dH*Ak<JH@/?BeFma*r0\TJ%ba#eQ*3'7qR.B&alPF)V(sa3=n-*YcmdI:JQV#'Ei]QQ>:,?d"k'VQ@&n=r-B\F^/o(IQee!"-6Su;1[96Cg4Mjqtcn,8XX\5l:QUPiGQ#;QK_uSBM!YSb@Q+39DR2O0O:nJ.N]<)+8>6?]5CL^I>_h^+!]OhU<XpALd'*-Ao5_Ok,di,u"?Y>gUfIV;Kbl1Kp=d8KARCY8Sok8%=?Q*/1mAplQ`VSF$Ti5[jA:t\helFKE<i[Rf"@=qi$ECNdQr"+ilIKEfb6?2WEf6#u^T3_S(I6TF!N8)<ffa!KlR;fP4G>mgVa;g4kXm!f[kiPWB7PZsXGpt]DRlh:XoVst#(YMJRVcJuDqQXH"MN5Pco&iDG,G>ESjLAM-2-!JT+YocXgZQ*QIt_*@V;T$m(9?GDcVtSIHn7&c_Ek/I:juH@p:mc1*G.ABs/'uXMiSR@"@&nBX&kl@Q=FiH]-RZ2eKO*Yta#<]2a4D"]uj*J9>c1lf/be4,93Z3Fpm_oo.u4Yats4H4D[A]u@(".mqD#(h*q>Qa7NOYq`htWi[Dl+K[B`>%;X`!e@Ga(o'0D!0+%!/!mtb)ZqP^XN'k.)J^dta$%mhB)6B&Wh&p`mKboFfrbEnbOsZE+KUNCLPN+VkN3N,]4+SUrZa3EL7$fJ!7p65)OB/f0d!_:OUJBWC,*?&!N]K-1b<FqF<B*c+ua,C>_$t-J;-^7*JLTR\\#.q,QQEW,4)lf&9_2DoIaoo>O^g>,JY55Y#,;R*<X*'$ioTJ6u5t:>L&R'l_AM_mj`"".C-hN]Lb,Aou5=U7*b\e#i"'Yru5Ce4R[b%Cfr]`&[q?'r\pRfF5Uk-;m7iV3<6.-MqP3K3:G9W7].I%[`r!%&U*CB=P&J$fCdkGhfHj1`iRJkKQXtY#1m(c204)!Y,Mo(So0@%@km8_@KCA))qB+])e=ppN-YO[l3W.-:*<"hJH--pV@5W\okEl!O["aNs/.Z\lSI^dk-R*dPoUm/MEkhs[]cVC5[1tjVZWlfpX9/KT(g\7V=))K8^4^T]01\3h6m<,W<FQWggb3_JOojo,p>ae"L3^]fL$g=`U&luY\PQck]-m+F_B8:Q"ZYqS,o]u%5GIn!8(ilOKbgGUG%S7?C*O&?.K#,XWadI7qXP[f<_d`NM#pjIL1Dil1<G:b%m>XNj[t\9s(h![GJ(7=bqiTLc*3t=,]#[<SDuUD@Q4-F"E!8NEXXA@<W40BZP$HkV:#S4,ZrP)kpPP/qfcO!<%kbW`q]:2a;^u!HbjWd%rQg[oPY`D<P73oV"TRQOpK-6TH4,'MJVEHFNt@klU4_C(Ss;LtJp5Q)^0=<`86%cO*WX.o98]=u7tgY.3Bg(.r,#FDVSi@L[J#fREaIbRf:V7e#qh1k`7]rV/r(U;:ol]bC.pc%>*iZWf)u\m@Y>euOMsrkY^t"i0t:`9]R(0tY-<)R_RtHJ\XQk1s.Xm*6bdG7Gen/r,hM3Q)hZ%_5HImH*6e&@4HRR,'cUloT.UH'c!#K\HB1:%sCHb8\;NDZK*5N3FgfOH$a*>qUuYeu!HS]'Duq0?3W]Mf&9qoe,:u1:6/>jp&(j1?AU(fP3o5=,:_LL/TdL)D\?[hE6R.?]a_pN-Gge]Cc6,lNK,T?@,>+(W]\YXH*P#\i5.^D_^S7`j@5I_VY/M('*ZH&U*D-1q6km$)M6-Dp3).^j04<eZd=Dg0C0e@b0]P_QLG%YW`g&lT%@Lr1hUo08[Yk$ESR)7"&,Me(_W<Reju~>endstream
|
||||
endobj
|
||||
xref
|
||||
0 9
|
||||
0000000000 65535 f
|
||||
0000000073 00000 n
|
||||
0000000114 00000 n
|
||||
0000000221 00000 n
|
||||
0000000330 00000 n
|
||||
0000000533 00000 n
|
||||
0000000601 00000 n
|
||||
0000000936 00000 n
|
||||
0000000995 00000 n
|
||||
trailer
|
||||
<<
|
||||
/ID
|
||||
[<292de92922b99d411f4915f95f3215c2><292de92922b99d411f4915f95f3215c2>]
|
||||
% ReportLab generated PDF document -- digest (http://www.reportlab.com)
|
||||
|
||||
/Info 6 0 R
|
||||
/Root 5 0 R
|
||||
/Size 9
|
||||
>>
|
||||
startxref
|
||||
3342
|
||||
%%EOF
|
||||
33
setup/generateTabletopPDF/A3_10Arucos_50mm_Seet224.json
Normal file
33
setup/generateTabletopPDF/A3_10Arucos_50mm_Seet224.json
Normal file
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"page_format": "A3",
|
||||
"orientation": "portrait",
|
||||
"page_size_mm": {
|
||||
"width": 297.0,
|
||||
"height": 420.0
|
||||
},
|
||||
"seed": 224,
|
||||
"num_arucos": 10,
|
||||
"aruco_size_mm": 50.0,
|
||||
"aruco_dictionary": "DICT_4X4_250",
|
||||
"aruco_start_id": 46,
|
||||
"page_border_margin_mm": 10.0,
|
||||
"forbidden_rectangle_mm": {
|
||||
"x": 143.5,
|
||||
"y": 205.0,
|
||||
"w": 10.0,
|
||||
"h": 10.0
|
||||
},
|
||||
"forbidden_rectangle_margin_mm": 30.0,
|
||||
"placements": [
|
||||
{"id": 46, "position": [-103.49, -14.02, -27.3], "normal": [0, 0, 1], "spin": 90},
|
||||
{"id": 47, "position": [17.33, -11.06, -27.3], "normal": [0, 0, 1], "spin": 90},
|
||||
{"id": 48, "position": [14.91, 182.28, -27.3], "normal": [0, 0, 1], "spin": 90},
|
||||
{"id": 49, "position": [-133.58, 94.98, -27.3], "normal": [0, 0, 1], "spin": 90},
|
||||
{"id": 50, "position": [82.51, 132.7, -27.3], "normal": [0, 0, 1], "spin": 90},
|
||||
{"id": 51, "position": [147.43, 136.07, -27.3], "normal": [0, 0, 1], "spin": 90},
|
||||
{"id": 52, "position": [159.13, -7.87, -27.3], "normal": [0, 0, 1], "spin": 90},
|
||||
{"id": 53, "position": [-151.21, 155.25, -27.3], "normal": [0, 0, 1], "spin": 90},
|
||||
{"id": 54, "position": [80.2, 56.76, -27.3], "normal": [0, 0, 1], "spin": 90},
|
||||
{"id": 55, "position": [94.41, -7.18, -27.3], "normal": [0, 0, 1], "spin": 90}
|
||||
]
|
||||
}
|
||||
74
setup/generateTabletopPDF/A3_10Arucos_50mm_Seet224.pdf
Normal file
74
setup/generateTabletopPDF/A3_10Arucos_50mm_Seet224.pdf
Normal file
@@ -0,0 +1,74 @@
|
||||
%PDF-1.3
|
||||
%“Œ‹ž ReportLab Generated PDF document http://www.reportlab.com
|
||||
1 0 obj
|
||||
<<
|
||||
/F1 2 0 R /F2 3 0 R
|
||||
>>
|
||||
endobj
|
||||
2 0 obj
|
||||
<<
|
||||
/BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font
|
||||
>>
|
||||
endobj
|
||||
3 0 obj
|
||||
<<
|
||||
/BaseFont /Times-Roman /Encoding /WinAnsiEncoding /Name /F2 /Subtype /Type1 /Type /Font
|
||||
>>
|
||||
endobj
|
||||
4 0 obj
|
||||
<<
|
||||
/Contents 8 0 R /MediaBox [ 0 0 841.8898 1190.551 ] /Parent 7 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
5 0 obj
|
||||
<<
|
||||
/PageMode /UseNone /Pages 7 0 R /Type /Catalog
|
||||
>>
|
||||
endobj
|
||||
6 0 obj
|
||||
<<
|
||||
/Author (OpenAI) /CreationDate (D:20260609100923+02'00') /Creator (ReportLab PDF Library - www.reportlab.com) /Keywords () /ModDate (D:20260609100923+02'00') /Producer (ReportLab PDF Library - www.reportlab.com)
|
||||
/Subject (A0 poster with random ArUco placement) /Title (A3_10Arucos_50mm_Seet224) /Trapped /False
|
||||
>>
|
||||
endobj
|
||||
7 0 obj
|
||||
<<
|
||||
/Count 1 /Kids [ 4 0 R ] /Type /Pages
|
||||
>>
|
||||
endobj
|
||||
8 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2250
|
||||
>>
|
||||
stream
|
||||
Gasb_c&UjC%#"*@'L!jj:dg/icS[tN92<UA6')NeAET%I?9_ED,.k)fZS91([ejia<Cs'$fgA9\++!k^a-Yi]nbr7AB0)t2n"1-M`5hbYa%u/BqNCjUqq'TC"!('LN&_BBR?NH@p#kl@dp%:6qqEcB-&p',g[UnbJoaSu;a4ehU0RIA,Frc-0DbM%hrpOOVid%+B4;qBq""25\"5W<3W@E%ID!:i6lW*_ie.,%H)^[!2C[0;g+/JMe5$Z`I<.!)H7dZS4-0:3lcN+#F]S/t7j9gMYc[!98*<Od'up'm,PsA6/6%ZZ=X@]TZDLYp(B<J9M2u/tP_h8?J0es0eQ*fYF"/pf%FhE'PDlcA-WuW8?p0:n&f2:8j:JYh"2L?aJ2M7"le\q=#.`P2f>[l"9gd-nL7tLL%>OEW=N_Lh[pFT-]cp&?4m9C1lc6,l-KdJAF,3uF!FYX=,OD"c#\uFglsimhX_UXuWEi/K^.R#B6+'u28J9RVaHtpKDVlQ)Iin-O(B&5p/\IjcTp8Eg\.-!tQ76h9%^AH#7ZYa(19@4.Ye?j\o7YaFC!6X`Q@fC=\MkoAK\bJrG'cU/m1!IZ>LOn#n85krj+ae8+_G#4cH?.M2Lp/UehR>0di5WrQaD@GP8DUSYVmR'f";$bH&Ghs`^@?Y\R-Sj2h^".#@Vfd#eP.CfUYCN`q>PkP,R9q7/C%Nqb-m(l?UlO7pCCecpR;c,T,9Y(jdnI4*BAJ'4Q;kVlHnj#g99U</@q%6n^]t5%`u.YhL,4RU5'bVH5E6U-?Th!pgseKJd[&CZC/RQ33erEmP=cI@'$qYa.Z#\i5-sDh&&`7Pj>A>Q_d1L6sh-XFjem&<\]`mX,UicSL*Z*"[;V&@,`HD6mI[os-nTjK,cLV:O3];I+,ZjXR8#?-gF"DU"Rg`9J0s.N#3r<%;CZXrBaS6&=%s\IXLEb(1u6Ed)ssjUecd/S8:,aI:sG7\ZCXU).84\0:54L9Q$[&9d4:Yb"5Bo0Z%Y!,FRpXQ9Gdjt13$RpHpr_F(s:XEanMdi5WrQhhR*U).330>Q:D!3FF:J@-@@2QY*GE_4J8Yq?Gt?(N]lR>4+MXViG?_id-FU("<7,cQC#dLo+fLt+A[pBe)V=2"U,L9Qt2S`/FmjaaSi)o.nKJ9AOggiIPM!5$8\@Ei$#]!n&LD\DKK\R'eD]eSi!A(L=URkOMaQ]D<cRU1O#k68Z4\!V;V=g+c:_n6#ON>oZ:%KL9)`2r,5Z!jaL.At*>H5a,]4g=#!>!"_Rk4LSVog-&NBI%I)A%ABt7Y02=WDggh0iP38p+U$"h+U`a@QfLS\LeK<!FX/VbduYu28/>TVUW6/`8h!`<Z@e\2F^tflZt>C?&H/C(u6tk7U9ec\lNDp11_YN*+:F%J2LsoldfOr^$.t2>8s6V5QInI!3hak^fh4&lT%@L>W3U-'"&L92X4F(l(r9XQ:;HLXn1i(j_jo.Bre<52T/Wk\]kJddWL6dN#e^pW\/o(>BJtpCDd9-*Q'r;S@;M6@R/R_%:Dm_^m\NU-=]3>Jk1]#QOpJM5tANPPnfN#^m\MYh3.kIh-/$k253L8A(CX'T+_KG0uU.tMqMt7EAr;_W<1W^G$@>dD%0qJ#Qq0I&@1gH/ubbeT%EqfGu`/\;E8=U;Ok@og;hh"Mkb_b,)2b+O5eq6NR7@r<Bdl16^4ViaTFi:[A]oEkYhPoe?jM1a7g96a?CK"=dL(CE4ssrkM$[3l*qo6FPM"+;"!bD5E0U&533?^XuFq9p!.\EYhjT%He\VP1a'gr%Jlc]kH/(>!3eOlJ@0b:G+V&+CH4e!I/euU,uM#fCPb2);15E37E\Y:\"U?GUFE[!>?]6K>E:99CgGek]jU3\>+R8VgGm<$WYJsom=WGhYCJRT5o[OEDstAa%Pd9q!S6lE)ReK`b5'O=0GXr+`r]\Y2h^#D!7nht/'6rY8iRRdlT"sHjT)NN"2J(MJ2bY;>GTYapWPaTR.VF!eoCamo*4D9Dq]H%F7V!W(:3E<@*KY+g)'pnjUe\*%Bm11'$JdiRH`G?jO`_eCM0i2W<N>$l1^o&6%r/6k.KDr!#=%u\pXQ!23l_Ya(WT<3[(F*cpS]>qo=hC!&84d6e%reW(C9E=&S2:G)hU`^tK>"X;Qq!G8@MsNJ;U/rDnLZ"2LA\R!#qo]B-1O98;4Zs!AOi9`i,!">,A7<D3J0mYQS#Hf?17*XRAi(a9a*U6\HHE2'f?@VaLZH)cG9g])/R!CZ~>endstream
|
||||
endobj
|
||||
xref
|
||||
0 9
|
||||
0000000000 65535 f
|
||||
0000000073 00000 n
|
||||
0000000114 00000 n
|
||||
0000000221 00000 n
|
||||
0000000330 00000 n
|
||||
0000000533 00000 n
|
||||
0000000601 00000 n
|
||||
0000000936 00000 n
|
||||
0000000995 00000 n
|
||||
trailer
|
||||
<<
|
||||
/ID
|
||||
[<544804e2078c10a71a717f7cd03ded29><544804e2078c10a71a717f7cd03ded29>]
|
||||
% ReportLab generated PDF document -- digest (http://www.reportlab.com)
|
||||
|
||||
/Info 6 0 R
|
||||
/Root 5 0 R
|
||||
/Size 9
|
||||
>>
|
||||
startxref
|
||||
3336
|
||||
%%EOF
|
||||
417
setup/generateTabletopPDF/a3_aruco.py
Normal file
417
setup/generateTabletopPDF/a3_aruco.py
Normal file
@@ -0,0 +1,417 @@
|
||||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import random
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import List, Tuple
|
||||
|
||||
from reportlab.pdfgen import canvas
|
||||
from reportlab.lib.units import mm
|
||||
from reportlab.pdfbase import pdfmetrics
|
||||
|
||||
try:
|
||||
import cv2
|
||||
except ImportError as exc:
|
||||
raise SystemExit(
|
||||
"OpenCV ist erforderlich. Installiere es mit: pip install opencv-contrib-python"
|
||||
) from exc
|
||||
|
||||
|
||||
# =========================
|
||||
# Header / Parameter
|
||||
# =========================
|
||||
|
||||
PAGE_FORMAT = "A3" # z.B. A0, A1, A2, A3, A4
|
||||
ORIENTATION = "portrait" # "portrait" oder "landscape"
|
||||
|
||||
NUM_ARUCOS = 10
|
||||
ARUCO_SIZE_MM = 50.0
|
||||
ARUCO_START_ID = 46 # erster Marker aus DICT_4X4_250
|
||||
SEED = 224 # Zufalls-Seed für reproduzierbare Verteilung
|
||||
|
||||
PAGE_BORDER_MARGIN_MM = 10.0 # Abstand aller Marker vom Seitenrand
|
||||
|
||||
FORBIDDEN_RECT_W_MM = 10.0
|
||||
FORBIDDEN_RECT_H_MM = 10.0
|
||||
FORBIDDEN_RECT_MARGIN_MM = 30.0 # keine ArUcos innerhalb dieses Abstands
|
||||
|
||||
LINE_WIDTH_MM = 1.0 # Linienstärke des Rechtecks
|
||||
TEXT_FONT = "Times-Roman"
|
||||
TEXT_SIZE_PT = 8
|
||||
TEXT_GAP_MM = 4.0
|
||||
|
||||
OUTPUT_BASENAME = f"{PAGE_FORMAT}_{NUM_ARUCOS}Arucos_{int(ARUCO_SIZE_MM)}mm_Seet{SEED}"
|
||||
|
||||
|
||||
# =========================
|
||||
# DIN-Formate
|
||||
# =========================
|
||||
|
||||
DIN_SIZES_MM = {
|
||||
"A0": (841.0, 1189.0),
|
||||
"A1": (594.0, 841.0),
|
||||
"A2": (420.0, 594.0),
|
||||
"A3": (297.0, 420.0),
|
||||
"A4": (210.0, 297.0),
|
||||
}
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class RectMM:
|
||||
x: float
|
||||
y: float
|
||||
w: float
|
||||
h: float
|
||||
|
||||
def intersects(self, other: "RectMM") -> bool:
|
||||
return not (
|
||||
self.x + self.w <= other.x
|
||||
or other.x + other.w <= self.x
|
||||
or self.y + self.h <= other.y
|
||||
or other.y + other.h <= self.y
|
||||
)
|
||||
|
||||
|
||||
def mm_to_pt(value_mm: float) -> float:
|
||||
return value_mm * mm
|
||||
|
||||
|
||||
def get_page_size_mm(page_format: str, orientation: str) -> Tuple[float, float]:
|
||||
if page_format not in DIN_SIZES_MM:
|
||||
raise ValueError(f"Unbekanntes Format: {page_format}. Unterstützt: {sorted(DIN_SIZES_MM)}")
|
||||
|
||||
w_mm, h_mm = DIN_SIZES_MM[page_format]
|
||||
if orientation.lower() == "portrait":
|
||||
return w_mm, h_mm
|
||||
if orientation.lower() == "landscape":
|
||||
return h_mm, w_mm
|
||||
raise ValueError("ORIENTATION muss 'portrait' oder 'landscape' sein.")
|
||||
|
||||
|
||||
def centered_rect(page_w_mm: float, page_h_mm: float, rect_w_mm: float, rect_h_mm: float) -> RectMM:
|
||||
return RectMM(
|
||||
x=(page_w_mm - rect_w_mm) / 2.0,
|
||||
y=(page_h_mm - rect_h_mm) / 2.0,
|
||||
w=rect_w_mm,
|
||||
h=rect_h_mm,
|
||||
)
|
||||
|
||||
|
||||
def expand_rect(rect: RectMM, margin_mm: float) -> RectMM:
|
||||
return RectMM(
|
||||
x=rect.x - margin_mm,
|
||||
y=rect.y - margin_mm,
|
||||
w=rect.w + 2.0 * margin_mm,
|
||||
h=rect.h + 2.0 * margin_mm,
|
||||
)
|
||||
|
||||
|
||||
def get_aruco_dictionary():
|
||||
# DICT_4X4_250 hat IDs 0..249
|
||||
return cv2.aruco.getPredefinedDictionary(cv2.aruco.DICT_4X4_250)
|
||||
|
||||
|
||||
def marker_module_pattern(marker_id: int) -> List[List[int]]:
|
||||
"""
|
||||
Liefert ein 6x6-Raster (inkl. schwarzem Rand).
|
||||
1 = schwarz, 0 = weiss
|
||||
"""
|
||||
aruco_dict = get_aruco_dictionary()
|
||||
img = cv2.aruco.generateImageMarker(aruco_dict, marker_id, 600) # nur zum Abtasten
|
||||
modules = 6
|
||||
cell = img.shape[0] // modules
|
||||
|
||||
pattern: List[List[int]] = []
|
||||
for r in range(modules):
|
||||
row: List[int] = []
|
||||
for c in range(modules):
|
||||
cy = int((r + 0.5) * cell)
|
||||
cx = int((c + 0.5) * cell)
|
||||
pixel = int(img[cy, cx]) # 0 = schwarz, 255 = weiss
|
||||
row.append(1 if pixel < 128 else 0)
|
||||
pattern.append(row)
|
||||
return pattern
|
||||
|
||||
|
||||
def draw_aruco_vector(
|
||||
c: canvas.Canvas,
|
||||
x_mm: float,
|
||||
y_mm: float,
|
||||
size_mm: float,
|
||||
marker_id: int,
|
||||
page_h_mm: float,
|
||||
) -> None:
|
||||
"""
|
||||
Zeichnet den Marker als Vektor-Rechtecke.
|
||||
x_mm, y_mm = linke obere Ecke in mm.
|
||||
"""
|
||||
pattern = marker_module_pattern(marker_id)
|
||||
modules = len(pattern)
|
||||
cell_mm = size_mm / modules
|
||||
|
||||
for r in range(modules):
|
||||
for col in range(modules):
|
||||
if pattern[r][col] == 1:
|
||||
cell_x_mm = x_mm + col * cell_mm
|
||||
#cell_y_mm = y_mm + (modules - 1 - r) * cell_mm
|
||||
cell_y_mm = y_mm + r * cell_mm
|
||||
c.rect(
|
||||
mm_to_pt(cell_x_mm),
|
||||
mm_to_pt(page_h_mm - cell_y_mm - cell_mm),
|
||||
mm_to_pt(cell_mm),
|
||||
mm_to_pt(cell_mm),
|
||||
stroke=0,
|
||||
fill=1,
|
||||
)
|
||||
|
||||
|
||||
def draw_aruco_label(
|
||||
c: canvas.Canvas,
|
||||
x_mm: float,
|
||||
y_mm: float,
|
||||
size_mm: float,
|
||||
page_h_mm: float,
|
||||
marker_id: int,
|
||||
) -> None:
|
||||
c.setFont(TEXT_FONT, TEXT_SIZE_PT)
|
||||
text = str(marker_id)
|
||||
text_width_pt = pdfmetrics.stringWidth(text, TEXT_FONT, TEXT_SIZE_PT)
|
||||
text_x_pt = mm_to_pt(x_mm + size_mm / 2.0) - text_width_pt / 2.0
|
||||
font = pdfmetrics.getFont(TEXT_FONT)
|
||||
ascent_mm = (font.face.ascent / 1000.0) * TEXT_SIZE_PT * 0.352777777777778
|
||||
text_baseline_y_mm = y_mm + size_mm + TEXT_GAP_MM + ascent_mm
|
||||
c.drawString(text_x_pt, mm_to_pt(page_h_mm - text_baseline_y_mm), text)
|
||||
|
||||
|
||||
def place_markers(
|
||||
page_w_mm: float,
|
||||
page_h_mm: float,
|
||||
num_markers: int,
|
||||
marker_size_mm: float,
|
||||
border_margin_mm: float,
|
||||
forbidden_area: RectMM,
|
||||
forbidden_margin_mm: float,
|
||||
seed: int,
|
||||
) -> List[dict]:
|
||||
rng = random.Random(seed)
|
||||
placed: List[RectMM] = []
|
||||
result: List[dict] = []
|
||||
|
||||
allowed_x_min = border_margin_mm
|
||||
allowed_y_min = border_margin_mm
|
||||
allowed_x_max = page_w_mm - border_margin_mm - marker_size_mm
|
||||
allowed_y_max = page_h_mm - border_margin_mm - marker_size_mm
|
||||
|
||||
if allowed_x_max < allowed_x_min or allowed_y_max < allowed_y_min:
|
||||
raise RuntimeError("Das Poster ist zu klein für Randabstand und Markergrösse.")
|
||||
|
||||
excluded = expand_rect(forbidden_area, forbidden_margin_mm)
|
||||
|
||||
attempts = 0
|
||||
max_attempts = 200000
|
||||
|
||||
while len(result) < num_markers and attempts < max_attempts:
|
||||
attempts += 1
|
||||
x = rng.uniform(allowed_x_min, allowed_x_max)
|
||||
y = rng.uniform(allowed_y_min, allowed_y_max)
|
||||
candidate = RectMM(x=x, y=y, w=marker_size_mm, h=marker_size_mm)
|
||||
|
||||
if any(candidate.intersects(other) for other in placed):
|
||||
continue
|
||||
if candidate.intersects(excluded):
|
||||
continue
|
||||
|
||||
placed.append(candidate)
|
||||
result.append(
|
||||
{
|
||||
"id": ARUCO_START_ID + len(result),
|
||||
"x_mm": round(x, 2),
|
||||
"y_mm": round(y, 2),
|
||||
"size_mm": marker_size_mm,
|
||||
}
|
||||
)
|
||||
|
||||
if len(result) < num_markers:
|
||||
raise RuntimeError(
|
||||
f"Nicht alle Marker konnten platziert werden: {len(result)} von {num_markers} "
|
||||
f"(nach {attempts} Versuchen)."
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def main() -> None:
|
||||
page_w_mm, page_h_mm = get_page_size_mm(PAGE_FORMAT, ORIENTATION)
|
||||
|
||||
if ARUCO_START_ID < 0 or ARUCO_START_ID + NUM_ARUCOS > 250:
|
||||
raise ValueError("ARUCO_START_ID + NUM_ARUCOS muss in den Bereich 0..249 von DICT_4X4_250 fallen.")
|
||||
|
||||
forbidden_rect = centered_rect(page_w_mm, page_h_mm, FORBIDDEN_RECT_W_MM, FORBIDDEN_RECT_H_MM)
|
||||
|
||||
# Neues lokales Koordinatensystem
|
||||
origin_x_mm = forbidden_rect.x + forbidden_rect.w - 90.0
|
||||
origin_y_mm = forbidden_rect.y
|
||||
|
||||
placements = place_markers(
|
||||
page_w_mm=page_w_mm,
|
||||
page_h_mm=page_h_mm,
|
||||
num_markers=NUM_ARUCOS,
|
||||
marker_size_mm=ARUCO_SIZE_MM,
|
||||
border_margin_mm=PAGE_BORDER_MARGIN_MM,
|
||||
forbidden_area=forbidden_rect,
|
||||
forbidden_margin_mm=FORBIDDEN_RECT_MARGIN_MM,
|
||||
seed=SEED,
|
||||
)
|
||||
|
||||
pdf_path = Path(f"{OUTPUT_BASENAME}.pdf")
|
||||
json_path = Path(f"{OUTPUT_BASENAME}.json")
|
||||
|
||||
c = canvas.Canvas(str(pdf_path), pagesize=(mm_to_pt(page_w_mm), mm_to_pt(page_h_mm)))
|
||||
c.setTitle(pdf_path.stem)
|
||||
c.setAuthor("OpenAI")
|
||||
c.setSubject("A0 poster with random ArUco placement")
|
||||
|
||||
# Weisser Hintergrund
|
||||
c.setFillColorRGB(1, 1, 1)
|
||||
c.rect(0, 0, mm_to_pt(page_w_mm), mm_to_pt(page_h_mm), stroke=0, fill=1)
|
||||
|
||||
# Rechteck mit 1 mm schwarzer Linie
|
||||
c.setStrokeColorRGB(0, 0, 0)
|
||||
c.setLineWidth(mm_to_pt(LINE_WIDTH_MM))
|
||||
c.rect(
|
||||
mm_to_pt(forbidden_rect.x),
|
||||
mm_to_pt(page_h_mm - forbidden_rect.y - forbidden_rect.h),
|
||||
mm_to_pt(forbidden_rect.w),
|
||||
mm_to_pt(forbidden_rect.h),
|
||||
stroke=1,
|
||||
fill=0,
|
||||
)
|
||||
|
||||
# Koordinatensystem
|
||||
|
||||
ARROW_LEN_MM = 50.0
|
||||
|
||||
# X-Achse (rot, nach unten)
|
||||
c.setStrokeColorRGB(1, 0, 0)
|
||||
c.setLineWidth(mm_to_pt(1.0))
|
||||
|
||||
c.line(
|
||||
mm_to_pt(origin_x_mm),
|
||||
mm_to_pt(page_h_mm - origin_y_mm),
|
||||
mm_to_pt(origin_x_mm),
|
||||
mm_to_pt(page_h_mm - (origin_y_mm + ARROW_LEN_MM)),
|
||||
)
|
||||
|
||||
# Pfeilspitze
|
||||
c.line(
|
||||
mm_to_pt(origin_x_mm),
|
||||
mm_to_pt(page_h_mm - (origin_y_mm + ARROW_LEN_MM)),
|
||||
mm_to_pt(origin_x_mm - 4),
|
||||
mm_to_pt(page_h_mm - (origin_y_mm + ARROW_LEN_MM - 4)),
|
||||
)
|
||||
c.line(
|
||||
mm_to_pt(origin_x_mm),
|
||||
mm_to_pt(page_h_mm - (origin_y_mm + ARROW_LEN_MM)),
|
||||
mm_to_pt(origin_x_mm + 4),
|
||||
mm_to_pt(page_h_mm - (origin_y_mm + ARROW_LEN_MM - 4)),
|
||||
)
|
||||
|
||||
# Y-Achse (grün, nach rechts)
|
||||
c.setStrokeColorRGB(0, 0.7, 0)
|
||||
|
||||
c.line(
|
||||
mm_to_pt(origin_x_mm),
|
||||
mm_to_pt(page_h_mm - origin_y_mm),
|
||||
mm_to_pt(origin_x_mm + ARROW_LEN_MM),
|
||||
mm_to_pt(page_h_mm - origin_y_mm),
|
||||
)
|
||||
|
||||
# Pfeilspitze
|
||||
c.line(
|
||||
mm_to_pt(origin_x_mm + ARROW_LEN_MM),
|
||||
mm_to_pt(page_h_mm - origin_y_mm),
|
||||
mm_to_pt(origin_x_mm + ARROW_LEN_MM - 4),
|
||||
mm_to_pt(page_h_mm - origin_y_mm - 4),
|
||||
)
|
||||
c.line(
|
||||
mm_to_pt(origin_x_mm + ARROW_LEN_MM),
|
||||
mm_to_pt(page_h_mm - origin_y_mm),
|
||||
mm_to_pt(origin_x_mm + ARROW_LEN_MM - 4),
|
||||
mm_to_pt(page_h_mm - origin_y_mm + 4),
|
||||
)
|
||||
|
||||
c.setStrokeColorRGB(0, 0, 0)
|
||||
|
||||
# ArUcos zeichnen
|
||||
c.setFillColorRGB(0, 0, 0)
|
||||
for item in placements:
|
||||
draw_aruco_vector(
|
||||
c=c,
|
||||
x_mm=item["x_mm"],
|
||||
y_mm=item["y_mm"],
|
||||
size_mm=item["size_mm"],
|
||||
marker_id=item["id"],
|
||||
page_h_mm=page_h_mm,
|
||||
)
|
||||
draw_aruco_label(
|
||||
c=c,
|
||||
x_mm=item["x_mm"],
|
||||
y_mm=item["y_mm"],
|
||||
size_mm=item["size_mm"],
|
||||
page_h_mm=page_h_mm,
|
||||
marker_id=item["id"],
|
||||
)
|
||||
|
||||
c.showPage()
|
||||
c.save()
|
||||
|
||||
# JSON mit Positionen
|
||||
with json_path.open("w", encoding="utf-8") as f:
|
||||
meta = {
|
||||
"page_format": PAGE_FORMAT,
|
||||
"orientation": ORIENTATION,
|
||||
"page_size_mm": {"width": page_w_mm, "height": page_h_mm},
|
||||
"seed": SEED,
|
||||
"num_arucos": NUM_ARUCOS,
|
||||
"aruco_size_mm": ARUCO_SIZE_MM,
|
||||
"aruco_dictionary": "DICT_4X4_250",
|
||||
"aruco_start_id": ARUCO_START_ID,
|
||||
"page_border_margin_mm": PAGE_BORDER_MARGIN_MM,
|
||||
"forbidden_rectangle_mm": {
|
||||
"x": round(forbidden_rect.x, 2),
|
||||
"y": round(forbidden_rect.y, 2),
|
||||
"w": forbidden_rect.w,
|
||||
"h": forbidden_rect.h,
|
||||
},
|
||||
"forbidden_rectangle_margin_mm": FORBIDDEN_RECT_MARGIN_MM,
|
||||
}
|
||||
|
||||
f.write(json.dumps(meta, indent=2, ensure_ascii=False)[:-2])
|
||||
f.write(',\n "placements": [\n')
|
||||
|
||||
for index, p in enumerate(placements):
|
||||
item = {
|
||||
"id": p["id"],
|
||||
"position": [
|
||||
round((p["y_mm"] + ARUCO_SIZE_MM / 2) - origin_y_mm, 2),
|
||||
-1*round(origin_x_mm - (p["x_mm"] + ARUCO_SIZE_MM / 2), 2),
|
||||
-27.3,
|
||||
],
|
||||
"normal": [0, 0, 1],
|
||||
"spin": 90,
|
||||
}
|
||||
line = json.dumps(item, ensure_ascii=False)
|
||||
if index < len(placements) - 1:
|
||||
line += ","
|
||||
f.write(f" {line}\n")
|
||||
|
||||
f.write(" ]\n}\n")
|
||||
|
||||
print(f"PDF geschrieben: {pdf_path.resolve()}")
|
||||
print(f"JSON geschrieben: {json_path.resolve()}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user