diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 9e90981..4718038 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -5,4 +5,4 @@ RUN apt-get update && \ rm -rf /var/lib/apt/lists/* # Flask prototype dependencies plus plotting/dxf helpers reused by the GUI -RUN pip3 install --no-cache-dir flask flask-cors numpy matplotlib ezdxf +RUN pip3 install --no-cache-dir flask flask-cors numpy matplotlib ezdxf pytest diff --git a/docs/assets/dxf/induction_demo/air_regions.dxf b/docs/assets/dxf/induction_demo/air_regions.dxf new file mode 100644 index 0000000..9f476d2 --- /dev/null +++ b/docs/assets/dxf/induction_demo/air_regions.dxf @@ -0,0 +1,3352 @@ + 0 +SECTION + 2 +HEADER + 9 +$ACADVER + 1 +AC1024 + 9 +$ACADMAINTVER + 70 +6 + 9 +$DWGCODEPAGE + 3 +ANSI_1252 + 9 +$LASTSAVEDBY + 1 +ezdxf + 9 +$INSBASE + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$EXTMIN + 10 +1e+20 + 20 +1e+20 + 30 +1e+20 + 9 +$EXTMAX + 10 +-1e+20 + 20 +-1e+20 + 30 +-1e+20 + 9 +$LIMMIN + 10 +0.0 + 20 +0.0 + 9 +$LIMMAX + 10 +420.0 + 20 +297.0 + 9 +$ORTHOMODE + 70 +0 + 9 +$REGENMODE + 70 +1 + 9 +$FILLMODE + 70 +1 + 9 +$QTEXTMODE + 70 +0 + 9 +$MIRRTEXT + 70 +1 + 9 +$LTSCALE + 40 +1.0 + 9 +$ATTMODE + 70 +1 + 9 +$TEXTSIZE + 40 +2.5 + 9 +$TRACEWID + 40 +1.0 + 9 +$TEXTSTYLE + 7 +Standard + 9 +$CLAYER + 8 +0 + 9 +$CELTYPE + 6 +ByLayer + 9 +$CECOLOR + 62 +256 + 9 +$CELTSCALE + 40 +1.0 + 9 +$DISPSILH + 70 +0 + 9 +$DIMSCALE + 40 +1.0 + 9 +$DIMASZ + 40 +2.5 + 9 +$DIMEXO + 40 +0.625 + 9 +$DIMDLI + 40 +3.75 + 9 +$DIMRND + 40 +0.0 + 9 +$DIMDLE + 40 +0.0 + 9 +$DIMEXE + 40 +1.25 + 9 +$DIMTP + 40 +0.0 + 9 +$DIMTM + 40 +0.0 + 9 +$DIMTXT + 40 +2.5 + 9 +$DIMCEN + 40 +2.5 + 9 +$DIMTSZ + 40 +0.0 + 9 +$DIMTOL + 70 +0 + 9 +$DIMLIM + 70 +0 + 9 +$DIMTIH + 70 +0 + 9 +$DIMTOH + 70 +0 + 9 +$DIMSE1 + 70 +0 + 9 +$DIMSE2 + 70 +0 + 9 +$DIMTAD + 70 +1 + 9 +$DIMZIN + 70 +8 + 9 +$DIMBLK + 1 + + 9 +$DIMASO + 70 +1 + 9 +$DIMSHO + 70 +1 + 9 +$DIMPOST + 1 + + 9 +$DIMAPOST + 1 + + 9 +$DIMALT + 70 +0 + 9 +$DIMALTD + 70 +3 + 9 +$DIMALTF + 40 +0.03937007874 + 9 +$DIMLFAC + 40 +1.0 + 9 +$DIMTOFL + 70 +1 + 9 +$DIMTVP + 40 +0.0 + 9 +$DIMTIX + 70 +0 + 9 +$DIMSOXD + 70 +0 + 9 +$DIMSAH + 70 +0 + 9 +$DIMBLK1 + 1 + + 9 +$DIMBLK2 + 1 + + 9 +$DIMSTYLE + 2 +ISO-25 + 9 +$DIMCLRD + 70 +0 + 9 +$DIMCLRE + 70 +0 + 9 +$DIMCLRT + 70 +0 + 9 +$DIMTFAC + 40 +1.0 + 9 +$DIMGAP + 40 +0.625 + 9 +$DIMJUST + 70 +0 + 9 +$DIMSD1 + 70 +0 + 9 +$DIMSD2 + 70 +0 + 9 +$DIMTOLJ + 70 +0 + 9 +$DIMTZIN + 70 +8 + 9 +$DIMALTZ + 70 +0 + 9 +$DIMALTTZ + 70 +0 + 9 +$DIMUPT + 70 +0 + 9 +$DIMDEC + 70 +2 + 9 +$DIMTDEC + 70 +2 + 9 +$DIMALTU + 70 +2 + 9 +$DIMALTTD + 70 +3 + 9 +$DIMTXSTY + 7 +Standard + 9 +$DIMAUNIT + 70 +0 + 9 +$DIMADEC + 70 +0 + 9 +$DIMALTRND + 40 +0.0 + 9 +$DIMAZIN + 70 +0 + 9 +$DIMDSEP + 70 +44 + 9 +$DIMATFIT + 70 +3 + 9 +$DIMFRAC + 70 +0 + 9 +$DIMLDRBLK + 1 + + 9 +$DIMLUNIT + 70 +2 + 9 +$DIMLWD + 70 +-2 + 9 +$DIMLWE + 70 +-2 + 9 +$DIMTMOVE + 70 +0 + 9 +$DIMFXL + 40 +1.0 + 9 +$DIMFXLON + 70 +0 + 9 +$DIMJOGANG + 40 +0.785398163397 + 9 +$DIMTFILL + 70 +0 + 9 +$DIMTFILLCLR + 70 +0 + 9 +$DIMARCSYM + 70 +0 + 9 +$DIMLTYPE + 6 + + 9 +$DIMLTEX1 + 6 + + 9 +$DIMLTEX2 + 6 + + 9 +$DIMTXTDIRECTION + 70 +0 + 9 +$LUNITS + 70 +2 + 9 +$LUPREC + 70 +4 + 9 +$SKETCHINC + 40 +1.0 + 9 +$FILLETRAD + 40 +10.0 + 9 +$AUNITS + 70 +0 + 9 +$AUPREC + 70 +2 + 9 +$MENU + 1 +. + 9 +$ELEVATION + 40 +0.0 + 9 +$PELEVATION + 40 +0.0 + 9 +$THICKNESS + 40 +0.0 + 9 +$LIMCHECK + 70 +0 + 9 +$CHAMFERA + 40 +0.0 + 9 +$CHAMFERB + 40 +0.0 + 9 +$CHAMFERC + 40 +0.0 + 9 +$CHAMFERD + 40 +0.0 + 9 +$SKPOLY + 70 +0 + 9 +$TDCREATE + 40 +2460989.8777777776 + 9 +$TDUCREATE + 40 +2458532.153996898 + 9 +$TDUPDATE + 40 +2460989.8777777776 + 9 +$TDUUPDATE + 40 +2458532.1544311 + 9 +$TDINDWG + 40 +0.0 + 9 +$TDUSRTIMER + 40 +0.0 + 9 +$USRTIMER + 70 +1 + 9 +$ANGBASE + 50 +0.0 + 9 +$ANGDIR + 70 +0 + 9 +$PDMODE + 70 +0 + 9 +$PDSIZE + 40 +0.0 + 9 +$PLINEWID + 40 +0.0 + 9 +$SPLFRAME + 70 +0 + 9 +$SPLINETYPE + 70 +6 + 9 +$SPLINESEGS + 70 +8 + 9 +$HANDSEED + 5 +39 + 9 +$SURFTAB1 + 70 +6 + 9 +$SURFTAB2 + 70 +6 + 9 +$SURFTYPE + 70 +6 + 9 +$SURFU + 70 +6 + 9 +$SURFV + 70 +6 + 9 +$UCSBASE + 2 + + 9 +$UCSNAME + 2 + + 9 +$UCSORG + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$UCSXDIR + 10 +1.0 + 20 +0.0 + 30 +0.0 + 9 +$UCSYDIR + 10 +0.0 + 20 +1.0 + 30 +0.0 + 9 +$UCSORTHOREF + 2 + + 9 +$UCSORTHOVIEW + 70 +0 + 9 +$UCSORGTOP + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$UCSORGBOTTOM + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$UCSORGLEFT + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$UCSORGRIGHT + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$UCSORGFRONT + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$UCSORGBACK + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$PUCSBASE + 2 + + 9 +$PUCSNAME + 2 + + 9 +$PUCSORG + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$PUCSXDIR + 10 +1.0 + 20 +0.0 + 30 +0.0 + 9 +$PUCSYDIR + 10 +0.0 + 20 +1.0 + 30 +0.0 + 9 +$PUCSORTHOREF + 2 + + 9 +$PUCSORTHOVIEW + 70 +0 + 9 +$PUCSORGTOP + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$PUCSORGBOTTOM + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$PUCSORGLEFT + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$PUCSORGRIGHT + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$PUCSORGFRONT + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$PUCSORGBACK + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$USERI1 + 70 +0 + 9 +$USERI2 + 70 +0 + 9 +$USERI3 + 70 +0 + 9 +$USERI4 + 70 +0 + 9 +$USERI5 + 70 +0 + 9 +$USERR1 + 40 +0.0 + 9 +$USERR2 + 40 +0.0 + 9 +$USERR3 + 40 +0.0 + 9 +$USERR4 + 40 +0.0 + 9 +$USERR5 + 40 +0.0 + 9 +$WORLDVIEW + 70 +1 + 9 +$SHADEDGE + 70 +3 + 9 +$SHADEDIF + 70 +70 + 9 +$TILEMODE + 70 +1 + 9 +$MAXACTVP + 70 +64 + 9 +$PINSBASE + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$PLIMCHECK + 70 +0 + 9 +$PEXTMIN + 10 +1e+20 + 20 +1e+20 + 30 +1e+20 + 9 +$PEXTMAX + 10 +-1e+20 + 20 +-1e+20 + 30 +-1e+20 + 9 +$PLIMMIN + 10 +0.0 + 20 +0.0 + 9 +$PLIMMAX + 10 +420.0 + 20 +297.0 + 9 +$UNITMODE + 70 +0 + 9 +$VISRETAIN + 70 +1 + 9 +$PLINEGEN + 70 +0 + 9 +$PSLTSCALE + 70 +1 + 9 +$TREEDEPTH + 70 +3020 + 9 +$CMLSTYLE + 2 +Standard + 9 +$CMLJUST + 70 +0 + 9 +$CMLSCALE + 40 +20.0 + 9 +$PROXYGRAPHICS + 70 +1 + 9 +$MEASUREMENT + 70 +1 + 9 +$CELWEIGHT +370 +-1 + 9 +$ENDCAPS +280 +0 + 9 +$JOINSTYLE +280 +0 + 9 +$LWDISPLAY +290 +0 + 9 +$INSUNITS + 70 +6 + 9 +$HYPERLINKBASE + 1 + + 9 +$STYLESHEET + 1 + + 9 +$XEDIT +290 +1 + 9 +$CEPSNTYPE +380 +0 + 9 +$PSTYLEMODE +290 +1 + 9 +$FINGERPRINTGUID + 2 +{D5386D6A-308F-49E9-8EE8-9B02EF3401A2} + 9 +$VERSIONGUID + 2 +{2C2AC8A7-E551-4936-983C-FA9EFAB7F728} + 9 +$EXTNAMES +290 +1 + 9 +$PSVPSCALE + 40 +0.0 + 9 +$OLESTARTUP +290 +0 + 9 +$SORTENTS +280 +127 + 9 +$INDEXCTL +280 +0 + 9 +$HIDETEXT +280 +1 + 9 +$XCLIPFRAME +280 +1 + 9 +$HALOGAP +280 +0 + 9 +$OBSCOLOR + 70 +257 + 9 +$OBSLTYPE +280 +0 + 9 +$INTERSECTIONDISPLAY +280 +0 + 9 +$INTERSECTIONCOLOR + 70 +257 + 9 +$DIMASSOC +280 +2 + 9 +$PROJECTNAME + 1 + + 9 +$CAMERADISPLAY +290 +0 + 9 +$LENSLENGTH + 40 +50.0 + 9 +$CAMERAHEIGHT + 40 +0.0 + 9 +$STEPSPERSEC + 40 +24.0 + 9 +$STEPSIZE + 40 +100.0 + 9 +$3DDWFPREC + 40 +2.0 + 9 +$PSOLWIDTH + 40 +0.005 + 9 +$PSOLHEIGHT + 40 +0.08 + 9 +$LOFTANG1 + 40 +1.570796326795 + 9 +$LOFTANG2 + 40 +1.570796326795 + 9 +$LOFTMAG1 + 40 +0.0 + 9 +$LOFTMAG2 + 40 +0.0 + 9 +$LOFTPARAM + 70 +7 + 9 +$LOFTNORMALS +280 +1 + 9 +$LATITUDE + 40 +37.795 + 9 +$LONGITUDE + 40 +-122.394 + 9 +$NORTHDIRECTION + 40 +0.0 + 9 +$TIMEZONE + 70 +-8000 + 9 +$LIGHTGLYPHDISPLAY +280 +1 + 9 +$TILEMODELIGHTSYNCH +280 +1 + 9 +$CMATERIAL +347 +20 + 9 +$SOLIDHIST +280 +0 + 9 +$SHOWHIST +280 +1 + 9 +$DWFFRAME +280 +2 + 9 +$DGNFRAME +280 +2 + 9 +$REALWORLDSCALE +290 +1 + 9 +$INTERFERECOLOR + 62 +256 + 9 +$CSHADOW +280 +0 + 9 +$SHADOWPLANELOCATION + 40 +0.0 + 0 +ENDSEC + 0 +SECTION + 2 +CLASSES + 0 +CLASS + 1 +ACDBDICTIONARYWDFLT + 2 +AcDbDictionaryWithDefault + 3 +ObjectDBX Classes + 90 +0 + 91 +0 +280 +0 +281 +0 + 0 +CLASS + 1 +SUN + 2 +AcDbSun + 3 +SCENEOE + 90 +1153 + 91 +0 +280 +0 +281 +0 + 0 +CLASS + 1 +VISUALSTYLE + 2 +AcDbVisualStyle + 3 +ObjectDBX Classes + 90 +4095 + 91 +0 +280 +0 +281 +0 + 0 +CLASS + 1 +MATERIAL + 2 +AcDbMaterial + 3 +ObjectDBX Classes + 90 +1153 + 91 +0 +280 +0 +281 +0 + 0 +CLASS + 1 +SCALE + 2 +AcDbScale + 3 +ObjectDBX Classes + 90 +1153 + 91 +0 +280 +0 +281 +0 + 0 +CLASS + 1 +TABLESTYLE + 2 +AcDbTableStyle + 3 +ObjectDBX Classes + 90 +4095 + 91 +0 +280 +0 +281 +0 + 0 +CLASS + 1 +MLEADERSTYLE + 2 +AcDbMLeaderStyle + 3 +ACDB_MLEADERSTYLE_CLASS + 90 +4095 + 91 +0 +280 +0 +281 +0 + 0 +CLASS + 1 +DICTIONARYVAR + 2 +AcDbDictionaryVar + 3 +ObjectDBX Classes + 90 +0 + 91 +0 +280 +0 +281 +0 + 0 +CLASS + 1 +CELLSTYLEMAP + 2 +AcDbCellStyleMap + 3 +ObjectDBX Classes + 90 +1152 + 91 +0 +280 +0 +281 +0 + 0 +CLASS + 1 +MENTALRAYRENDERSETTINGS + 2 +AcDbMentalRayRenderSettings + 3 +SCENEOE + 90 +1024 + 91 +0 +280 +0 +281 +0 + 0 +CLASS + 1 +ACDBDETAILVIEWSTYLE + 2 +AcDbDetailViewStyle + 3 +ObjectDBX Classes + 90 +1025 + 91 +0 +280 +0 +281 +0 + 0 +CLASS + 1 +ACDBSECTIONVIEWSTYLE + 2 +AcDbSectionViewStyle + 3 +ObjectDBX Classes + 90 +1025 + 91 +0 +280 +0 +281 +0 + 0 +CLASS + 1 +RASTERVARIABLES + 2 +AcDbRasterVariables + 3 +ISM + 90 +0 + 91 +0 +280 +0 +281 +0 + 0 +CLASS + 1 +LAYOUT + 2 +AcDbLayout + 3 +ObjectDBX Classes + 90 +0 + 91 +0 +280 +0 +281 +0 + 0 +CLASS + 1 +ACDBPLACEHOLDER + 2 +AcDbPlaceHolder + 3 +ObjectDBX Classes + 90 +0 + 91 +0 +280 +0 +281 +0 + 0 +ENDSEC + 0 +SECTION + 2 +TABLES + 0 +TABLE + 2 +VPORT + 5 +8 +330 +0 +100 +AcDbSymbolTable + 70 +1 + 0 +VPORT + 5 +23 +330 +8 +100 +AcDbSymbolTableRecord +100 +AcDbViewportTableRecord + 2 +*Active + 70 +0 + 10 +0.0 + 20 +0.0 + 11 +1.0 + 21 +1.0 + 12 +0.0 + 22 +0.0 + 13 +0.0 + 23 +0.0 + 14 +0.5 + 24 +0.5 + 15 +0.5 + 25 +0.5 + 16 +0.0 + 26 +0.0 + 36 +1.0 + 17 +0.0 + 27 +0.0 + 37 +0.0 + 40 +1000.0 + 41 +1.34 + 42 +50.0 + 43 +0.0 + 44 +0.0 + 50 +0.0 + 51 +0.0 + 71 +0 + 72 +1000 + 73 +1 + 74 +3 + 75 +0 + 76 +0 + 77 +0 + 78 +0 +281 +0 + 65 +0 +146 +0.0 + 0 +ENDTAB + 0 +TABLE + 2 +LTYPE + 5 +2 +330 +0 +100 +AcDbSymbolTable + 70 +3 + 0 +LTYPE + 5 +24 +330 +2 +100 +AcDbSymbolTableRecord +100 +AcDbLinetypeTableRecord + 2 +ByBlock + 70 +0 + 3 + + 72 +65 + 73 +0 + 40 +0.0 + 0 +LTYPE + 5 +25 +330 +2 +100 +AcDbSymbolTableRecord +100 +AcDbLinetypeTableRecord + 2 +ByLayer + 70 +0 + 3 + + 72 +65 + 73 +0 + 40 +0.0 + 0 +LTYPE + 5 +26 +330 +2 +100 +AcDbSymbolTableRecord +100 +AcDbLinetypeTableRecord + 2 +Continuous + 70 +0 + 3 + + 72 +65 + 73 +0 + 40 +0.0 + 0 +ENDTAB + 0 +TABLE + 2 +LAYER + 5 +1 +330 +0 +100 +AcDbSymbolTable + 70 +2 + 0 +LAYER + 5 +27 +330 +1 +100 +AcDbSymbolTableRecord +100 +AcDbLayerTableRecord + 2 +0 + 70 +0 + 62 +7 + 6 +Continuous +370 +-3 +390 +13 +347 +21 + 0 +LAYER + 5 +28 +330 +1 +100 +AcDbSymbolTableRecord +100 +AcDbLayerTableRecord + 2 +Defpoints + 70 +0 + 62 +7 + 6 +Continuous +290 +0 +370 +-3 +390 +13 +347 +21 + 0 +ENDTAB + 0 +TABLE + 2 +STYLE + 5 +5 +330 +0 +100 +AcDbSymbolTable + 70 +1 + 0 +STYLE + 5 +29 +330 +5 +100 +AcDbSymbolTableRecord +100 +AcDbTextStyleTableRecord + 2 +Standard + 70 +0 + 40 +0.0 + 41 +1.0 + 50 +0.0 + 71 +0 + 42 +2.5 + 3 +txt + 4 + + 0 +ENDTAB + 0 +TABLE + 2 +VIEW + 5 +7 +330 +0 +100 +AcDbSymbolTable + 70 +0 + 0 +ENDTAB + 0 +TABLE + 2 +UCS + 5 +6 +330 +0 +100 +AcDbSymbolTable + 70 +0 + 0 +ENDTAB + 0 +TABLE + 2 +APPID + 5 +3 +330 +0 +100 +AcDbSymbolTable + 70 +3 + 0 +APPID + 5 +2A +330 +3 +100 +AcDbSymbolTableRecord +100 +AcDbRegAppTableRecord + 2 +ACAD + 70 +0 + 0 +APPID + 5 +36 +330 +3 +100 +AcDbSymbolTableRecord +100 +AcDbRegAppTableRecord + 2 +HATCHBACKGROUNDCOLOR + 70 +0 + 0 +APPID + 5 +37 +330 +3 +100 +AcDbSymbolTableRecord +100 +AcDbRegAppTableRecord + 2 +EZDXF + 70 +0 + 0 +ENDTAB + 0 +TABLE + 2 +DIMSTYLE + 5 +4 +330 +0 +100 +AcDbSymbolTable + 70 +1 +100 +AcDbDimStyleTable + 0 +DIMSTYLE +105 +2B +330 +4 +100 +AcDbSymbolTableRecord +100 +AcDbDimStyleTableRecord + 2 +Standard + 70 +0 + 40 +1.0 + 41 +2.5 + 42 +0.625 + 43 +3.75 + 44 +1.25 + 45 +0.0 + 46 +0.0 + 47 +0.0 + 48 +0.0 + 49 +2.5 +140 +2.5 +141 +2.5 +142 +0.0 +143 +0.03937007874 +144 +1.0 +145 +0.0 +146 +1.0 +147 +0.625 +148 +0.0 + 69 +0 + 70 +0 + 71 +0 + 72 +0 + 73 +0 + 74 +0 + 75 +0 + 76 +0 + 77 +1 + 78 +8 + 79 +3 +170 +0 +171 +3 +172 +1 +173 +0 +174 +0 +175 +0 +176 +0 +177 +0 +178 +0 +179 +2 +271 +2 +272 +2 +273 +2 +274 +3 +275 +0 +276 +0 +277 +2 +278 +44 +279 +0 +280 +0 +281 +0 +282 +0 +283 +0 +284 +8 +285 +0 +286 +0 +288 +0 +289 +3 +290 +0 +371 +-2 +372 +-2 + 0 +ENDTAB + 0 +TABLE + 2 +BLOCK_RECORD + 5 +9 +330 +0 +100 +AcDbSymbolTable + 70 +2 + 0 +BLOCK_RECORD + 5 +17 +330 +9 +100 +AcDbSymbolTableRecord +100 +AcDbBlockTableRecord + 2 +*Model_Space +340 +1A + 70 +0 +280 +1 +281 +0 + 0 +BLOCK_RECORD + 5 +1B +330 +9 +100 +AcDbSymbolTableRecord +100 +AcDbBlockTableRecord + 2 +*Paper_Space +340 +1E + 70 +0 +280 +1 +281 +0 + 0 +ENDTAB + 0 +ENDSEC + 0 +SECTION + 2 +BLOCKS + 0 +BLOCK + 5 +18 +330 +17 +100 +AcDbEntity + 8 +0 +100 +AcDbBlockBegin + 2 +*Model_Space + 70 +0 + 10 +0.0 + 20 +0.0 + 30 +0.0 + 3 +*Model_Space + 1 + + 0 +ENDBLK + 5 +19 +330 +17 +100 +AcDbEntity + 8 +0 +100 +AcDbBlockEnd + 0 +BLOCK + 5 +1C +330 +1B +100 +AcDbEntity + 8 +0 +100 +AcDbBlockBegin + 2 +*Paper_Space + 70 +0 + 10 +0.0 + 20 +0.0 + 30 +0.0 + 3 +*Paper_Space + 1 + + 0 +ENDBLK + 5 +1D +330 +1B +100 +AcDbEntity + 8 +0 +100 +AcDbBlockEnd + 0 +ENDSEC + 0 +SECTION + 2 +ENTITIES + 0 +LWPOLYLINE + 5 +2F +330 +17 +100 +AcDbEntity + 8 +material_air +100 +AcDbPolyline + 90 +5 + 70 +0 + 10 +0.0395 + 20 +-0.0063 + 10 +0.0494 + 20 +-0.0078 + 10 +0.0494 + 20 +0.0078 + 10 +0.0395 + 20 +0.0063 + 10 +0.0395 + 20 +-0.0063 + 0 +LWPOLYLINE + 5 +30 +330 +17 +100 +AcDbEntity + 8 +material_air +100 +AcDbPolyline + 90 +5 + 70 +0 + 10 +0.0252 + 20 +0.0311 + 10 +0.0315 + 20 +0.0389 + 10 +0.0179 + 20 +0.0467 + 10 +0.0143 + 20 +0.0373 + 10 +0.0252 + 20 +0.0311 + 0 +LWPOLYLINE + 5 +31 +330 +17 +100 +AcDbEntity + 8 +material_air +100 +AcDbPolyline + 90 +5 + 70 +0 + 10 +-0.0143 + 20 +0.0373 + 10 +-0.0179 + 20 +0.0467 + 10 +-0.0315 + 20 +0.0389 + 10 +-0.0252 + 20 +0.0311 + 10 +-0.0143 + 20 +0.0373 + 0 +LWPOLYLINE + 5 +32 +330 +17 +100 +AcDbEntity + 8 +material_air +100 +AcDbPolyline + 90 +5 + 70 +0 + 10 +-0.0395 + 20 +0.0063 + 10 +-0.0494 + 20 +0.0078 + 10 +-0.0494 + 20 +-0.0078 + 10 +-0.0395 + 20 +-0.0063 + 10 +-0.0395 + 20 +0.0063 + 0 +LWPOLYLINE + 5 +33 +330 +17 +100 +AcDbEntity + 8 +material_air +100 +AcDbPolyline + 90 +5 + 70 +0 + 10 +-0.0252 + 20 +-0.0311 + 10 +-0.0315 + 20 +-0.0389 + 10 +-0.0179 + 20 +-0.0467 + 10 +-0.0143 + 20 +-0.0373 + 10 +-0.0252 + 20 +-0.0311 + 0 +LWPOLYLINE + 5 +34 +330 +17 +100 +AcDbEntity + 8 +material_air +100 +AcDbPolyline + 90 +5 + 70 +0 + 10 +0.0143 + 20 +-0.0373 + 10 +0.0179 + 20 +-0.0467 + 10 +0.0315 + 20 +-0.0389 + 10 +0.0252 + 20 +-0.0311 + 10 +0.0143 + 20 +-0.0373 + 0 +LWPOLYLINE + 5 +35 +330 +17 +100 +AcDbEntity + 8 +material_air +100 +AcDbPolyline + 90 +19 + 70 +0 + 10 +0.04 + 20 +0.0 + 10 +0.0376 + 20 +0.0137 + 10 +0.0306 + 20 +0.0257 + 10 +0.02 + 20 +0.0346 + 10 +0.0069 + 20 +0.0394 + 10 +-0.0069 + 20 +0.0394 + 10 +-0.02 + 20 +0.0346 + 10 +-0.0306 + 20 +0.0257 + 10 +-0.0376 + 20 +0.0137 + 10 +-0.04 + 20 +0.0 + 10 +-0.0376 + 20 +-0.0137 + 10 +-0.0306 + 20 +-0.0257 + 10 +-0.02 + 20 +-0.0346 + 10 +-0.0069 + 20 +-0.0394 + 10 +0.0069 + 20 +-0.0394 + 10 +0.02 + 20 +-0.0346 + 10 +0.0306 + 20 +-0.0257 + 10 +0.0376 + 20 +-0.0137 + 10 +0.04 + 20 +0.0 + 0 +ENDSEC + 0 +SECTION + 2 +OBJECTS + 0 +DICTIONARY + 5 +A +330 +0 +100 +AcDbDictionary +281 +1 + 3 +ACAD_COLOR +350 +B + 3 +ACAD_GROUP +350 +C + 3 +ACAD_LAYOUT +350 +D + 3 +ACAD_MATERIAL +350 +E + 3 +ACAD_MLEADERSTYLE +350 +F + 3 +ACAD_MLINESTYLE +350 +10 + 3 +ACAD_PLOTSETTINGS +350 +11 + 3 +ACAD_PLOTSTYLENAME +350 +12 + 3 +ACAD_SCALELIST +350 +14 + 3 +ACAD_TABLESTYLE +350 +15 + 3 +ACAD_VISUALSTYLE +350 +16 + 3 +EZDXF_META +350 +2D + 0 +DICTIONARY + 5 +B +330 +A +100 +AcDbDictionary +281 +1 + 0 +DICTIONARY + 5 +C +330 +A +100 +AcDbDictionary +281 +1 + 0 +DICTIONARY + 5 +D +330 +A +100 +AcDbDictionary +281 +1 + 3 +Model +350 +1A + 3 +Layout1 +350 +1E + 0 +DICTIONARY + 5 +E +330 +A +100 +AcDbDictionary +281 +1 + 3 +ByBlock +350 +1F + 3 +ByLayer +350 +20 + 3 +Global +350 +21 + 0 +DICTIONARY + 5 +F +330 +A +100 +AcDbDictionary +281 +1 + 3 +Standard +350 +2C + 0 +DICTIONARY + 5 +10 +330 +A +100 +AcDbDictionary +281 +1 + 3 +Standard +350 +22 + 0 +DICTIONARY + 5 +11 +330 +A +100 +AcDbDictionary +281 +1 + 0 +ACDBDICTIONARYWDFLT + 5 +12 +330 +A +100 +AcDbDictionary +281 +1 + 3 +Normal +350 +13 +100 +AcDbDictionaryWithDefault +340 +13 + 0 +ACDBPLACEHOLDER + 5 +13 +330 +12 + 0 +DICTIONARY + 5 +14 +330 +A +100 +AcDbDictionary +281 +1 + 0 +DICTIONARY + 5 +15 +330 +A +100 +AcDbDictionary +281 +1 + 0 +DICTIONARY + 5 +16 +330 +A +100 +AcDbDictionary +281 +1 + 0 +LAYOUT + 5 +1A +330 +D +100 +AcDbPlotSettings + 1 + + 4 +A3 + 6 + + 40 +7.5 + 41 +20.0 + 42 +7.5 + 43 +20.0 + 44 +420.0 + 45 +297.0 + 46 +0.0 + 47 +0.0 + 48 +0.0 + 49 +0.0 +140 +0.0 +141 +0.0 +142 +1.0 +143 +1.0 + 70 +1024 + 72 +1 + 73 +0 + 74 +5 + 7 + + 75 +16 + 76 +0 + 77 +2 + 78 +300 +147 +1.0 +148 +0.0 +149 +0.0 +100 +AcDbLayout + 1 +Model + 70 +1 + 71 +0 + 10 +0.0 + 20 +0.0 + 11 +420.0 + 21 +297.0 + 12 +0.0 + 22 +0.0 + 32 +0.0 + 14 +1e+20 + 24 +1e+20 + 34 +1e+20 + 15 +-1e+20 + 25 +-1e+20 + 35 +-1e+20 +146 +0.0 + 13 +0.0 + 23 +0.0 + 33 +0.0 + 16 +1.0 + 26 +0.0 + 36 +0.0 + 17 +0.0 + 27 +1.0 + 37 +0.0 + 76 +1 +330 +17 + 0 +LAYOUT + 5 +1E +330 +D +100 +AcDbPlotSettings + 1 + + 4 +A3 + 6 + + 40 +7.5 + 41 +20.0 + 42 +7.5 + 43 +20.0 + 44 +420.0 + 45 +297.0 + 46 +0.0 + 47 +0.0 + 48 +0.0 + 49 +0.0 +140 +0.0 +141 +0.0 +142 +1.0 +143 +1.0 + 70 +0 + 72 +1 + 73 +0 + 74 +5 + 7 + + 75 +16 + 76 +0 + 77 +2 + 78 +300 +147 +1.0 +148 +0.0 +149 +0.0 +100 +AcDbLayout + 1 +Layout1 + 70 +1 + 71 +1 + 10 +0.0 + 20 +0.0 + 11 +420.0 + 21 +297.0 + 12 +0.0 + 22 +0.0 + 32 +0.0 + 14 +1e+20 + 24 +1e+20 + 34 +1e+20 + 15 +-1e+20 + 25 +-1e+20 + 35 +-1e+20 +146 +0.0 + 13 +0.0 + 23 +0.0 + 33 +0.0 + 16 +1.0 + 26 +0.0 + 36 +0.0 + 17 +0.0 + 27 +1.0 + 37 +0.0 + 76 +1 +330 +1B + 0 +MATERIAL + 5 +1F +102 +{ACAD_REACTORS +330 +E +102 +} +330 +E +100 +AcDbMaterial + 1 +ByBlock + 2 + + 70 +0 + 40 +1.0 + 71 +1 + 41 +1.0 + 91 +-1023410177 + 42 +1.0 + 72 +1 + 3 + + 73 +1 + 74 +1 + 75 +1 + 44 +0.5 + 73 +0 + 45 +1.0 + 46 +1.0 + 77 +1 + 4 + + 78 +1 + 79 +1 +170 +1 + 48 +1.0 +171 +1 + 6 + +172 +1 +173 +1 +174 +1 +140 +1.0 +141 +1.0 +175 +1 + 7 + +176 +1 +177 +1 +178 +1 +143 +1.0 +179 +1 + 8 + +270 +1 +271 +1 +272 +1 +145 +1.0 +146 +1.0 +273 +1 + 9 + +274 +1 +275 +1 +276 +1 + 42 +1.0 + 72 +1 + 3 + + 73 +1 + 74 +1 + 75 +1 + 94 +63 + 0 +MATERIAL + 5 +20 +102 +{ACAD_REACTORS +330 +E +102 +} +330 +E +100 +AcDbMaterial + 1 +ByLayer + 2 + + 70 +0 + 40 +1.0 + 71 +1 + 41 +1.0 + 91 +-1023410177 + 42 +1.0 + 72 +1 + 3 + + 73 +1 + 74 +1 + 75 +1 + 44 +0.5 + 73 +0 + 45 +1.0 + 46 +1.0 + 77 +1 + 4 + + 78 +1 + 79 +1 +170 +1 + 48 +1.0 +171 +1 + 6 + +172 +1 +173 +1 +174 +1 +140 +1.0 +141 +1.0 +175 +1 + 7 + +176 +1 +177 +1 +178 +1 +143 +1.0 +179 +1 + 8 + +270 +1 +271 +1 +272 +1 +145 +1.0 +146 +1.0 +273 +1 + 9 + +274 +1 +275 +1 +276 +1 + 42 +1.0 + 72 +1 + 3 + + 73 +1 + 74 +1 + 75 +1 + 94 +63 + 0 +MATERIAL + 5 +21 +102 +{ACAD_REACTORS +330 +E +102 +} +330 +E +100 +AcDbMaterial + 1 +Global + 2 + + 70 +0 + 40 +1.0 + 71 +1 + 41 +1.0 + 91 +-1023410177 + 42 +1.0 + 72 +1 + 3 + + 73 +1 + 74 +1 + 75 +1 + 44 +0.5 + 73 +0 + 45 +1.0 + 46 +1.0 + 77 +1 + 4 + + 78 +1 + 79 +1 +170 +1 + 48 +1.0 +171 +1 + 6 + +172 +1 +173 +1 +174 +1 +140 +1.0 +141 +1.0 +175 +1 + 7 + +176 +1 +177 +1 +178 +1 +143 +1.0 +179 +1 + 8 + +270 +1 +271 +1 +272 +1 +145 +1.0 +146 +1.0 +273 +1 + 9 + +274 +1 +275 +1 +276 +1 + 42 +1.0 + 72 +1 + 3 + + 73 +1 + 74 +1 + 75 +1 + 94 +63 + 0 +MLINESTYLE + 5 +22 +102 +{ACAD_REACTORS +330 +10 +102 +} +330 +10 +100 +AcDbMlineStyle + 2 +Standard + 70 +0 + 3 + + 62 +256 + 51 +90.0 + 52 +90.0 + 71 +2 + 49 +0.5 + 62 +256 + 6 +BYLAYER + 49 +-0.5 + 62 +256 + 6 +BYLAYER + 0 +MLEADERSTYLE + 5 +2C +102 +{ACAD_REACTORS +330 +F +102 +} +330 +F +100 +AcDbMLeaderStyle +179 +2 +170 +2 +171 +1 +172 +0 + 90 +2 + 40 +0.0 + 41 +0.0 +173 +1 + 91 +-1056964608 + 92 +-2 +290 +1 + 42 +2.0 +291 +1 + 43 +8.0 + 3 +Standard + 44 +4.0 +300 + +342 +29 +174 +1 +175 +1 +176 +0 +178 +1 + 93 +-1056964608 + 45 +4.0 +292 +0 +297 +0 + 46 +4.0 + 94 +-1056964608 + 47 +1.0 + 49 +1.0 +140 +1.0 +294 +1 +141 +0.0 +177 +0 +142 +1.0 +295 +0 +296 +0 +143 +3.75 +271 +0 +272 +9 +273 +9 + 0 +DICTIONARY + 5 +2D +330 +A +100 +AcDbDictionary +280 +1 +281 +1 + 3 +CREATED_BY_EZDXF +350 +2E + 3 +WRITTEN_BY_EZDXF +350 +38 + 0 +DICTIONARYVAR + 5 +2E +330 +2D +100 +DictionaryVariables +280 +0 + 1 +1.4.3 @ 2025-11-09T21:04:00.700723+00:00 + 0 +DICTIONARYVAR + 5 +38 +330 +2D +100 +DictionaryVariables +280 +0 + 1 +1.4.3 @ 2025-11-09T21:04:00.703583+00:00 + 0 +ENDSEC + 0 +EOF diff --git a/docs/assets/dxf/induction_demo/domain.dxf b/docs/assets/dxf/induction_demo/domain.dxf new file mode 100644 index 0000000..39c1c68 --- /dev/null +++ b/docs/assets/dxf/induction_demo/domain.dxf @@ -0,0 +1,3080 @@ + 0 +SECTION + 2 +HEADER + 9 +$ACADVER + 1 +AC1024 + 9 +$ACADMAINTVER + 70 +6 + 9 +$DWGCODEPAGE + 3 +ANSI_1252 + 9 +$LASTSAVEDBY + 1 +ezdxf + 9 +$INSBASE + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$EXTMIN + 10 +1e+20 + 20 +1e+20 + 30 +1e+20 + 9 +$EXTMAX + 10 +-1e+20 + 20 +-1e+20 + 30 +-1e+20 + 9 +$LIMMIN + 10 +0.0 + 20 +0.0 + 9 +$LIMMAX + 10 +420.0 + 20 +297.0 + 9 +$ORTHOMODE + 70 +0 + 9 +$REGENMODE + 70 +1 + 9 +$FILLMODE + 70 +1 + 9 +$QTEXTMODE + 70 +0 + 9 +$MIRRTEXT + 70 +1 + 9 +$LTSCALE + 40 +1.0 + 9 +$ATTMODE + 70 +1 + 9 +$TEXTSIZE + 40 +2.5 + 9 +$TRACEWID + 40 +1.0 + 9 +$TEXTSTYLE + 7 +Standard + 9 +$CLAYER + 8 +0 + 9 +$CELTYPE + 6 +ByLayer + 9 +$CECOLOR + 62 +256 + 9 +$CELTSCALE + 40 +1.0 + 9 +$DISPSILH + 70 +0 + 9 +$DIMSCALE + 40 +1.0 + 9 +$DIMASZ + 40 +2.5 + 9 +$DIMEXO + 40 +0.625 + 9 +$DIMDLI + 40 +3.75 + 9 +$DIMRND + 40 +0.0 + 9 +$DIMDLE + 40 +0.0 + 9 +$DIMEXE + 40 +1.25 + 9 +$DIMTP + 40 +0.0 + 9 +$DIMTM + 40 +0.0 + 9 +$DIMTXT + 40 +2.5 + 9 +$DIMCEN + 40 +2.5 + 9 +$DIMTSZ + 40 +0.0 + 9 +$DIMTOL + 70 +0 + 9 +$DIMLIM + 70 +0 + 9 +$DIMTIH + 70 +0 + 9 +$DIMTOH + 70 +0 + 9 +$DIMSE1 + 70 +0 + 9 +$DIMSE2 + 70 +0 + 9 +$DIMTAD + 70 +1 + 9 +$DIMZIN + 70 +8 + 9 +$DIMBLK + 1 + + 9 +$DIMASO + 70 +1 + 9 +$DIMSHO + 70 +1 + 9 +$DIMPOST + 1 + + 9 +$DIMAPOST + 1 + + 9 +$DIMALT + 70 +0 + 9 +$DIMALTD + 70 +3 + 9 +$DIMALTF + 40 +0.03937007874 + 9 +$DIMLFAC + 40 +1.0 + 9 +$DIMTOFL + 70 +1 + 9 +$DIMTVP + 40 +0.0 + 9 +$DIMTIX + 70 +0 + 9 +$DIMSOXD + 70 +0 + 9 +$DIMSAH + 70 +0 + 9 +$DIMBLK1 + 1 + + 9 +$DIMBLK2 + 1 + + 9 +$DIMSTYLE + 2 +ISO-25 + 9 +$DIMCLRD + 70 +0 + 9 +$DIMCLRE + 70 +0 + 9 +$DIMCLRT + 70 +0 + 9 +$DIMTFAC + 40 +1.0 + 9 +$DIMGAP + 40 +0.625 + 9 +$DIMJUST + 70 +0 + 9 +$DIMSD1 + 70 +0 + 9 +$DIMSD2 + 70 +0 + 9 +$DIMTOLJ + 70 +0 + 9 +$DIMTZIN + 70 +8 + 9 +$DIMALTZ + 70 +0 + 9 +$DIMALTTZ + 70 +0 + 9 +$DIMUPT + 70 +0 + 9 +$DIMDEC + 70 +2 + 9 +$DIMTDEC + 70 +2 + 9 +$DIMALTU + 70 +2 + 9 +$DIMALTTD + 70 +3 + 9 +$DIMTXSTY + 7 +Standard + 9 +$DIMAUNIT + 70 +0 + 9 +$DIMADEC + 70 +0 + 9 +$DIMALTRND + 40 +0.0 + 9 +$DIMAZIN + 70 +0 + 9 +$DIMDSEP + 70 +44 + 9 +$DIMATFIT + 70 +3 + 9 +$DIMFRAC + 70 +0 + 9 +$DIMLDRBLK + 1 + + 9 +$DIMLUNIT + 70 +2 + 9 +$DIMLWD + 70 +-2 + 9 +$DIMLWE + 70 +-2 + 9 +$DIMTMOVE + 70 +0 + 9 +$DIMFXL + 40 +1.0 + 9 +$DIMFXLON + 70 +0 + 9 +$DIMJOGANG + 40 +0.785398163397 + 9 +$DIMTFILL + 70 +0 + 9 +$DIMTFILLCLR + 70 +0 + 9 +$DIMARCSYM + 70 +0 + 9 +$DIMLTYPE + 6 + + 9 +$DIMLTEX1 + 6 + + 9 +$DIMLTEX2 + 6 + + 9 +$DIMTXTDIRECTION + 70 +0 + 9 +$LUNITS + 70 +2 + 9 +$LUPREC + 70 +4 + 9 +$SKETCHINC + 40 +1.0 + 9 +$FILLETRAD + 40 +10.0 + 9 +$AUNITS + 70 +0 + 9 +$AUPREC + 70 +2 + 9 +$MENU + 1 +. + 9 +$ELEVATION + 40 +0.0 + 9 +$PELEVATION + 40 +0.0 + 9 +$THICKNESS + 40 +0.0 + 9 +$LIMCHECK + 70 +0 + 9 +$CHAMFERA + 40 +0.0 + 9 +$CHAMFERB + 40 +0.0 + 9 +$CHAMFERC + 40 +0.0 + 9 +$CHAMFERD + 40 +0.0 + 9 +$SKPOLY + 70 +0 + 9 +$TDCREATE + 40 +2460989.8771064817 + 9 +$TDUCREATE + 40 +2458532.153996898 + 9 +$TDUPDATE + 40 +2460989.8771064817 + 9 +$TDUUPDATE + 40 +2458532.1544311 + 9 +$TDINDWG + 40 +0.0 + 9 +$TDUSRTIMER + 40 +0.0 + 9 +$USRTIMER + 70 +1 + 9 +$ANGBASE + 50 +0.0 + 9 +$ANGDIR + 70 +0 + 9 +$PDMODE + 70 +0 + 9 +$PDSIZE + 40 +0.0 + 9 +$PLINEWID + 40 +0.0 + 9 +$SPLFRAME + 70 +0 + 9 +$SPLINETYPE + 70 +6 + 9 +$SPLINESEGS + 70 +8 + 9 +$HANDSEED + 5 +33 + 9 +$SURFTAB1 + 70 +6 + 9 +$SURFTAB2 + 70 +6 + 9 +$SURFTYPE + 70 +6 + 9 +$SURFU + 70 +6 + 9 +$SURFV + 70 +6 + 9 +$UCSBASE + 2 + + 9 +$UCSNAME + 2 + + 9 +$UCSORG + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$UCSXDIR + 10 +1.0 + 20 +0.0 + 30 +0.0 + 9 +$UCSYDIR + 10 +0.0 + 20 +1.0 + 30 +0.0 + 9 +$UCSORTHOREF + 2 + + 9 +$UCSORTHOVIEW + 70 +0 + 9 +$UCSORGTOP + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$UCSORGBOTTOM + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$UCSORGLEFT + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$UCSORGRIGHT + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$UCSORGFRONT + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$UCSORGBACK + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$PUCSBASE + 2 + + 9 +$PUCSNAME + 2 + + 9 +$PUCSORG + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$PUCSXDIR + 10 +1.0 + 20 +0.0 + 30 +0.0 + 9 +$PUCSYDIR + 10 +0.0 + 20 +1.0 + 30 +0.0 + 9 +$PUCSORTHOREF + 2 + + 9 +$PUCSORTHOVIEW + 70 +0 + 9 +$PUCSORGTOP + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$PUCSORGBOTTOM + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$PUCSORGLEFT + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$PUCSORGRIGHT + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$PUCSORGFRONT + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$PUCSORGBACK + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$USERI1 + 70 +0 + 9 +$USERI2 + 70 +0 + 9 +$USERI3 + 70 +0 + 9 +$USERI4 + 70 +0 + 9 +$USERI5 + 70 +0 + 9 +$USERR1 + 40 +0.0 + 9 +$USERR2 + 40 +0.0 + 9 +$USERR3 + 40 +0.0 + 9 +$USERR4 + 40 +0.0 + 9 +$USERR5 + 40 +0.0 + 9 +$WORLDVIEW + 70 +1 + 9 +$SHADEDGE + 70 +3 + 9 +$SHADEDIF + 70 +70 + 9 +$TILEMODE + 70 +1 + 9 +$MAXACTVP + 70 +64 + 9 +$PINSBASE + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$PLIMCHECK + 70 +0 + 9 +$PEXTMIN + 10 +1e+20 + 20 +1e+20 + 30 +1e+20 + 9 +$PEXTMAX + 10 +-1e+20 + 20 +-1e+20 + 30 +-1e+20 + 9 +$PLIMMIN + 10 +0.0 + 20 +0.0 + 9 +$PLIMMAX + 10 +420.0 + 20 +297.0 + 9 +$UNITMODE + 70 +0 + 9 +$VISRETAIN + 70 +1 + 9 +$PLINEGEN + 70 +0 + 9 +$PSLTSCALE + 70 +1 + 9 +$TREEDEPTH + 70 +3020 + 9 +$CMLSTYLE + 2 +Standard + 9 +$CMLJUST + 70 +0 + 9 +$CMLSCALE + 40 +20.0 + 9 +$PROXYGRAPHICS + 70 +1 + 9 +$MEASUREMENT + 70 +1 + 9 +$CELWEIGHT +370 +-1 + 9 +$ENDCAPS +280 +0 + 9 +$JOINSTYLE +280 +0 + 9 +$LWDISPLAY +290 +0 + 9 +$INSUNITS + 70 +6 + 9 +$HYPERLINKBASE + 1 + + 9 +$STYLESHEET + 1 + + 9 +$XEDIT +290 +1 + 9 +$CEPSNTYPE +380 +0 + 9 +$PSTYLEMODE +290 +1 + 9 +$FINGERPRINTGUID + 2 +{EE07616D-5BD4-4D4B-AADE-A31A1D41A2C7} + 9 +$VERSIONGUID + 2 +{A0185DAF-64AC-4B9D-AC8A-0550564D253F} + 9 +$EXTNAMES +290 +1 + 9 +$PSVPSCALE + 40 +0.0 + 9 +$OLESTARTUP +290 +0 + 9 +$SORTENTS +280 +127 + 9 +$INDEXCTL +280 +0 + 9 +$HIDETEXT +280 +1 + 9 +$XCLIPFRAME +280 +1 + 9 +$HALOGAP +280 +0 + 9 +$OBSCOLOR + 70 +257 + 9 +$OBSLTYPE +280 +0 + 9 +$INTERSECTIONDISPLAY +280 +0 + 9 +$INTERSECTIONCOLOR + 70 +257 + 9 +$DIMASSOC +280 +2 + 9 +$PROJECTNAME + 1 + + 9 +$CAMERADISPLAY +290 +0 + 9 +$LENSLENGTH + 40 +50.0 + 9 +$CAMERAHEIGHT + 40 +0.0 + 9 +$STEPSPERSEC + 40 +24.0 + 9 +$STEPSIZE + 40 +100.0 + 9 +$3DDWFPREC + 40 +2.0 + 9 +$PSOLWIDTH + 40 +0.005 + 9 +$PSOLHEIGHT + 40 +0.08 + 9 +$LOFTANG1 + 40 +1.570796326795 + 9 +$LOFTANG2 + 40 +1.570796326795 + 9 +$LOFTMAG1 + 40 +0.0 + 9 +$LOFTMAG2 + 40 +0.0 + 9 +$LOFTPARAM + 70 +7 + 9 +$LOFTNORMALS +280 +1 + 9 +$LATITUDE + 40 +37.795 + 9 +$LONGITUDE + 40 +-122.394 + 9 +$NORTHDIRECTION + 40 +0.0 + 9 +$TIMEZONE + 70 +-8000 + 9 +$LIGHTGLYPHDISPLAY +280 +1 + 9 +$TILEMODELIGHTSYNCH +280 +1 + 9 +$CMATERIAL +347 +20 + 9 +$SOLIDHIST +280 +0 + 9 +$SHOWHIST +280 +1 + 9 +$DWFFRAME +280 +2 + 9 +$DGNFRAME +280 +2 + 9 +$REALWORLDSCALE +290 +1 + 9 +$INTERFERECOLOR + 62 +256 + 9 +$CSHADOW +280 +0 + 9 +$SHADOWPLANELOCATION + 40 +0.0 + 0 +ENDSEC + 0 +SECTION + 2 +CLASSES + 0 +CLASS + 1 +ACDBDICTIONARYWDFLT + 2 +AcDbDictionaryWithDefault + 3 +ObjectDBX Classes + 90 +0 + 91 +0 +280 +0 +281 +0 + 0 +CLASS + 1 +SUN + 2 +AcDbSun + 3 +SCENEOE + 90 +1153 + 91 +0 +280 +0 +281 +0 + 0 +CLASS + 1 +VISUALSTYLE + 2 +AcDbVisualStyle + 3 +ObjectDBX Classes + 90 +4095 + 91 +0 +280 +0 +281 +0 + 0 +CLASS + 1 +MATERIAL + 2 +AcDbMaterial + 3 +ObjectDBX Classes + 90 +1153 + 91 +0 +280 +0 +281 +0 + 0 +CLASS + 1 +SCALE + 2 +AcDbScale + 3 +ObjectDBX Classes + 90 +1153 + 91 +0 +280 +0 +281 +0 + 0 +CLASS + 1 +TABLESTYLE + 2 +AcDbTableStyle + 3 +ObjectDBX Classes + 90 +4095 + 91 +0 +280 +0 +281 +0 + 0 +CLASS + 1 +MLEADERSTYLE + 2 +AcDbMLeaderStyle + 3 +ACDB_MLEADERSTYLE_CLASS + 90 +4095 + 91 +0 +280 +0 +281 +0 + 0 +CLASS + 1 +DICTIONARYVAR + 2 +AcDbDictionaryVar + 3 +ObjectDBX Classes + 90 +0 + 91 +0 +280 +0 +281 +0 + 0 +CLASS + 1 +CELLSTYLEMAP + 2 +AcDbCellStyleMap + 3 +ObjectDBX Classes + 90 +1152 + 91 +0 +280 +0 +281 +0 + 0 +CLASS + 1 +MENTALRAYRENDERSETTINGS + 2 +AcDbMentalRayRenderSettings + 3 +SCENEOE + 90 +1024 + 91 +0 +280 +0 +281 +0 + 0 +CLASS + 1 +ACDBDETAILVIEWSTYLE + 2 +AcDbDetailViewStyle + 3 +ObjectDBX Classes + 90 +1025 + 91 +0 +280 +0 +281 +0 + 0 +CLASS + 1 +ACDBSECTIONVIEWSTYLE + 2 +AcDbSectionViewStyle + 3 +ObjectDBX Classes + 90 +1025 + 91 +0 +280 +0 +281 +0 + 0 +CLASS + 1 +RASTERVARIABLES + 2 +AcDbRasterVariables + 3 +ISM + 90 +0 + 91 +0 +280 +0 +281 +0 + 0 +CLASS + 1 +ACDBPLACEHOLDER + 2 +AcDbPlaceHolder + 3 +ObjectDBX Classes + 90 +0 + 91 +0 +280 +0 +281 +0 + 0 +CLASS + 1 +LAYOUT + 2 +AcDbLayout + 3 +ObjectDBX Classes + 90 +0 + 91 +0 +280 +0 +281 +0 + 0 +ENDSEC + 0 +SECTION + 2 +TABLES + 0 +TABLE + 2 +VPORT + 5 +8 +330 +0 +100 +AcDbSymbolTable + 70 +1 + 0 +VPORT + 5 +23 +330 +8 +100 +AcDbSymbolTableRecord +100 +AcDbViewportTableRecord + 2 +*Active + 70 +0 + 10 +0.0 + 20 +0.0 + 11 +1.0 + 21 +1.0 + 12 +0.0 + 22 +0.0 + 13 +0.0 + 23 +0.0 + 14 +0.5 + 24 +0.5 + 15 +0.5 + 25 +0.5 + 16 +0.0 + 26 +0.0 + 36 +1.0 + 17 +0.0 + 27 +0.0 + 37 +0.0 + 40 +1000.0 + 41 +1.34 + 42 +50.0 + 43 +0.0 + 44 +0.0 + 50 +0.0 + 51 +0.0 + 71 +0 + 72 +1000 + 73 +1 + 74 +3 + 75 +0 + 76 +0 + 77 +0 + 78 +0 +281 +0 + 65 +0 +146 +0.0 + 0 +ENDTAB + 0 +TABLE + 2 +LTYPE + 5 +2 +330 +0 +100 +AcDbSymbolTable + 70 +3 + 0 +LTYPE + 5 +24 +330 +2 +100 +AcDbSymbolTableRecord +100 +AcDbLinetypeTableRecord + 2 +ByBlock + 70 +0 + 3 + + 72 +65 + 73 +0 + 40 +0.0 + 0 +LTYPE + 5 +25 +330 +2 +100 +AcDbSymbolTableRecord +100 +AcDbLinetypeTableRecord + 2 +ByLayer + 70 +0 + 3 + + 72 +65 + 73 +0 + 40 +0.0 + 0 +LTYPE + 5 +26 +330 +2 +100 +AcDbSymbolTableRecord +100 +AcDbLinetypeTableRecord + 2 +Continuous + 70 +0 + 3 + + 72 +65 + 73 +0 + 40 +0.0 + 0 +ENDTAB + 0 +TABLE + 2 +LAYER + 5 +1 +330 +0 +100 +AcDbSymbolTable + 70 +2 + 0 +LAYER + 5 +27 +330 +1 +100 +AcDbSymbolTableRecord +100 +AcDbLayerTableRecord + 2 +0 + 70 +0 + 62 +7 + 6 +Continuous +370 +-3 +390 +13 +347 +21 + 0 +LAYER + 5 +28 +330 +1 +100 +AcDbSymbolTableRecord +100 +AcDbLayerTableRecord + 2 +Defpoints + 70 +0 + 62 +7 + 6 +Continuous +290 +0 +370 +-3 +390 +13 +347 +21 + 0 +ENDTAB + 0 +TABLE + 2 +STYLE + 5 +5 +330 +0 +100 +AcDbSymbolTable + 70 +1 + 0 +STYLE + 5 +29 +330 +5 +100 +AcDbSymbolTableRecord +100 +AcDbTextStyleTableRecord + 2 +Standard + 70 +0 + 40 +0.0 + 41 +1.0 + 50 +0.0 + 71 +0 + 42 +2.5 + 3 +txt + 4 + + 0 +ENDTAB + 0 +TABLE + 2 +VIEW + 5 +7 +330 +0 +100 +AcDbSymbolTable + 70 +0 + 0 +ENDTAB + 0 +TABLE + 2 +UCS + 5 +6 +330 +0 +100 +AcDbSymbolTable + 70 +0 + 0 +ENDTAB + 0 +TABLE + 2 +APPID + 5 +3 +330 +0 +100 +AcDbSymbolTable + 70 +3 + 0 +APPID + 5 +2A +330 +3 +100 +AcDbSymbolTableRecord +100 +AcDbRegAppTableRecord + 2 +ACAD + 70 +0 + 0 +APPID + 5 +30 +330 +3 +100 +AcDbSymbolTableRecord +100 +AcDbRegAppTableRecord + 2 +HATCHBACKGROUNDCOLOR + 70 +0 + 0 +APPID + 5 +31 +330 +3 +100 +AcDbSymbolTableRecord +100 +AcDbRegAppTableRecord + 2 +EZDXF + 70 +0 + 0 +ENDTAB + 0 +TABLE + 2 +DIMSTYLE + 5 +4 +330 +0 +100 +AcDbSymbolTable + 70 +1 +100 +AcDbDimStyleTable + 0 +DIMSTYLE +105 +2B +330 +4 +100 +AcDbSymbolTableRecord +100 +AcDbDimStyleTableRecord + 2 +Standard + 70 +0 + 40 +1.0 + 41 +2.5 + 42 +0.625 + 43 +3.75 + 44 +1.25 + 45 +0.0 + 46 +0.0 + 47 +0.0 + 48 +0.0 + 49 +2.5 +140 +2.5 +141 +2.5 +142 +0.0 +143 +0.03937007874 +144 +1.0 +145 +0.0 +146 +1.0 +147 +0.625 +148 +0.0 + 69 +0 + 70 +0 + 71 +0 + 72 +0 + 73 +0 + 74 +0 + 75 +0 + 76 +0 + 77 +1 + 78 +8 + 79 +3 +170 +0 +171 +3 +172 +1 +173 +0 +174 +0 +175 +0 +176 +0 +177 +0 +178 +0 +179 +2 +271 +2 +272 +2 +273 +2 +274 +3 +275 +0 +276 +0 +277 +2 +278 +44 +279 +0 +280 +0 +281 +0 +282 +0 +283 +0 +284 +8 +285 +0 +286 +0 +288 +0 +289 +3 +290 +0 +371 +-2 +372 +-2 + 0 +ENDTAB + 0 +TABLE + 2 +BLOCK_RECORD + 5 +9 +330 +0 +100 +AcDbSymbolTable + 70 +2 + 0 +BLOCK_RECORD + 5 +17 +330 +9 +100 +AcDbSymbolTableRecord +100 +AcDbBlockTableRecord + 2 +*Model_Space +340 +1A + 70 +0 +280 +1 +281 +0 + 0 +BLOCK_RECORD + 5 +1B +330 +9 +100 +AcDbSymbolTableRecord +100 +AcDbBlockTableRecord + 2 +*Paper_Space +340 +1E + 70 +0 +280 +1 +281 +0 + 0 +ENDTAB + 0 +ENDSEC + 0 +SECTION + 2 +BLOCKS + 0 +BLOCK + 5 +18 +330 +17 +100 +AcDbEntity + 8 +0 +100 +AcDbBlockBegin + 2 +*Model_Space + 70 +0 + 10 +0.0 + 20 +0.0 + 30 +0.0 + 3 +*Model_Space + 1 + + 0 +ENDBLK + 5 +19 +330 +17 +100 +AcDbEntity + 8 +0 +100 +AcDbBlockEnd + 0 +BLOCK + 5 +1C +330 +1B +100 +AcDbEntity + 8 +0 +100 +AcDbBlockBegin + 2 +*Paper_Space + 70 +0 + 10 +0.0 + 20 +0.0 + 30 +0.0 + 3 +*Paper_Space + 1 + + 0 +ENDBLK + 5 +1D +330 +1B +100 +AcDbEntity + 8 +0 +100 +AcDbBlockEnd + 0 +ENDSEC + 0 +SECTION + 2 +ENTITIES + 0 +LWPOLYLINE + 5 +2F +330 +17 +100 +AcDbEntity + 8 +domain +100 +AcDbPolyline + 90 +5 + 70 +0 + 10 +-0.07 + 20 +-0.07 + 10 +0.07 + 20 +-0.07 + 10 +0.07 + 20 +0.07 + 10 +-0.07 + 20 +0.07 + 10 +-0.07 + 20 +-0.07 + 0 +ENDSEC + 0 +SECTION + 2 +OBJECTS + 0 +DICTIONARY + 5 +A +330 +0 +100 +AcDbDictionary +281 +1 + 3 +ACAD_COLOR +350 +B + 3 +ACAD_GROUP +350 +C + 3 +ACAD_LAYOUT +350 +D + 3 +ACAD_MATERIAL +350 +E + 3 +ACAD_MLEADERSTYLE +350 +F + 3 +ACAD_MLINESTYLE +350 +10 + 3 +ACAD_PLOTSETTINGS +350 +11 + 3 +ACAD_PLOTSTYLENAME +350 +12 + 3 +ACAD_SCALELIST +350 +14 + 3 +ACAD_TABLESTYLE +350 +15 + 3 +ACAD_VISUALSTYLE +350 +16 + 3 +EZDXF_META +350 +2D + 0 +DICTIONARY + 5 +B +330 +A +100 +AcDbDictionary +281 +1 + 0 +DICTIONARY + 5 +C +330 +A +100 +AcDbDictionary +281 +1 + 0 +DICTIONARY + 5 +D +330 +A +100 +AcDbDictionary +281 +1 + 3 +Model +350 +1A + 3 +Layout1 +350 +1E + 0 +DICTIONARY + 5 +E +330 +A +100 +AcDbDictionary +281 +1 + 3 +ByBlock +350 +1F + 3 +ByLayer +350 +20 + 3 +Global +350 +21 + 0 +DICTIONARY + 5 +F +330 +A +100 +AcDbDictionary +281 +1 + 3 +Standard +350 +2C + 0 +DICTIONARY + 5 +10 +330 +A +100 +AcDbDictionary +281 +1 + 3 +Standard +350 +22 + 0 +DICTIONARY + 5 +11 +330 +A +100 +AcDbDictionary +281 +1 + 0 +ACDBDICTIONARYWDFLT + 5 +12 +330 +A +100 +AcDbDictionary +281 +1 + 3 +Normal +350 +13 +100 +AcDbDictionaryWithDefault +340 +13 + 0 +ACDBPLACEHOLDER + 5 +13 +330 +12 + 0 +DICTIONARY + 5 +14 +330 +A +100 +AcDbDictionary +281 +1 + 0 +DICTIONARY + 5 +15 +330 +A +100 +AcDbDictionary +281 +1 + 0 +DICTIONARY + 5 +16 +330 +A +100 +AcDbDictionary +281 +1 + 0 +LAYOUT + 5 +1A +330 +D +100 +AcDbPlotSettings + 1 + + 4 +A3 + 6 + + 40 +7.5 + 41 +20.0 + 42 +7.5 + 43 +20.0 + 44 +420.0 + 45 +297.0 + 46 +0.0 + 47 +0.0 + 48 +0.0 + 49 +0.0 +140 +0.0 +141 +0.0 +142 +1.0 +143 +1.0 + 70 +1024 + 72 +1 + 73 +0 + 74 +5 + 7 + + 75 +16 + 76 +0 + 77 +2 + 78 +300 +147 +1.0 +148 +0.0 +149 +0.0 +100 +AcDbLayout + 1 +Model + 70 +1 + 71 +0 + 10 +0.0 + 20 +0.0 + 11 +420.0 + 21 +297.0 + 12 +0.0 + 22 +0.0 + 32 +0.0 + 14 +1e+20 + 24 +1e+20 + 34 +1e+20 + 15 +-1e+20 + 25 +-1e+20 + 35 +-1e+20 +146 +0.0 + 13 +0.0 + 23 +0.0 + 33 +0.0 + 16 +1.0 + 26 +0.0 + 36 +0.0 + 17 +0.0 + 27 +1.0 + 37 +0.0 + 76 +1 +330 +17 + 0 +LAYOUT + 5 +1E +330 +D +100 +AcDbPlotSettings + 1 + + 4 +A3 + 6 + + 40 +7.5 + 41 +20.0 + 42 +7.5 + 43 +20.0 + 44 +420.0 + 45 +297.0 + 46 +0.0 + 47 +0.0 + 48 +0.0 + 49 +0.0 +140 +0.0 +141 +0.0 +142 +1.0 +143 +1.0 + 70 +0 + 72 +1 + 73 +0 + 74 +5 + 7 + + 75 +16 + 76 +0 + 77 +2 + 78 +300 +147 +1.0 +148 +0.0 +149 +0.0 +100 +AcDbLayout + 1 +Layout1 + 70 +1 + 71 +1 + 10 +0.0 + 20 +0.0 + 11 +420.0 + 21 +297.0 + 12 +0.0 + 22 +0.0 + 32 +0.0 + 14 +1e+20 + 24 +1e+20 + 34 +1e+20 + 15 +-1e+20 + 25 +-1e+20 + 35 +-1e+20 +146 +0.0 + 13 +0.0 + 23 +0.0 + 33 +0.0 + 16 +1.0 + 26 +0.0 + 36 +0.0 + 17 +0.0 + 27 +1.0 + 37 +0.0 + 76 +1 +330 +1B + 0 +MATERIAL + 5 +1F +102 +{ACAD_REACTORS +330 +E +102 +} +330 +E +100 +AcDbMaterial + 1 +ByBlock + 2 + + 70 +0 + 40 +1.0 + 71 +1 + 41 +1.0 + 91 +-1023410177 + 42 +1.0 + 72 +1 + 3 + + 73 +1 + 74 +1 + 75 +1 + 44 +0.5 + 73 +0 + 45 +1.0 + 46 +1.0 + 77 +1 + 4 + + 78 +1 + 79 +1 +170 +1 + 48 +1.0 +171 +1 + 6 + +172 +1 +173 +1 +174 +1 +140 +1.0 +141 +1.0 +175 +1 + 7 + +176 +1 +177 +1 +178 +1 +143 +1.0 +179 +1 + 8 + +270 +1 +271 +1 +272 +1 +145 +1.0 +146 +1.0 +273 +1 + 9 + +274 +1 +275 +1 +276 +1 + 42 +1.0 + 72 +1 + 3 + + 73 +1 + 74 +1 + 75 +1 + 94 +63 + 0 +MATERIAL + 5 +20 +102 +{ACAD_REACTORS +330 +E +102 +} +330 +E +100 +AcDbMaterial + 1 +ByLayer + 2 + + 70 +0 + 40 +1.0 + 71 +1 + 41 +1.0 + 91 +-1023410177 + 42 +1.0 + 72 +1 + 3 + + 73 +1 + 74 +1 + 75 +1 + 44 +0.5 + 73 +0 + 45 +1.0 + 46 +1.0 + 77 +1 + 4 + + 78 +1 + 79 +1 +170 +1 + 48 +1.0 +171 +1 + 6 + +172 +1 +173 +1 +174 +1 +140 +1.0 +141 +1.0 +175 +1 + 7 + +176 +1 +177 +1 +178 +1 +143 +1.0 +179 +1 + 8 + +270 +1 +271 +1 +272 +1 +145 +1.0 +146 +1.0 +273 +1 + 9 + +274 +1 +275 +1 +276 +1 + 42 +1.0 + 72 +1 + 3 + + 73 +1 + 74 +1 + 75 +1 + 94 +63 + 0 +MATERIAL + 5 +21 +102 +{ACAD_REACTORS +330 +E +102 +} +330 +E +100 +AcDbMaterial + 1 +Global + 2 + + 70 +0 + 40 +1.0 + 71 +1 + 41 +1.0 + 91 +-1023410177 + 42 +1.0 + 72 +1 + 3 + + 73 +1 + 74 +1 + 75 +1 + 44 +0.5 + 73 +0 + 45 +1.0 + 46 +1.0 + 77 +1 + 4 + + 78 +1 + 79 +1 +170 +1 + 48 +1.0 +171 +1 + 6 + +172 +1 +173 +1 +174 +1 +140 +1.0 +141 +1.0 +175 +1 + 7 + +176 +1 +177 +1 +178 +1 +143 +1.0 +179 +1 + 8 + +270 +1 +271 +1 +272 +1 +145 +1.0 +146 +1.0 +273 +1 + 9 + +274 +1 +275 +1 +276 +1 + 42 +1.0 + 72 +1 + 3 + + 73 +1 + 74 +1 + 75 +1 + 94 +63 + 0 +MLINESTYLE + 5 +22 +102 +{ACAD_REACTORS +330 +10 +102 +} +330 +10 +100 +AcDbMlineStyle + 2 +Standard + 70 +0 + 3 + + 62 +256 + 51 +90.0 + 52 +90.0 + 71 +2 + 49 +0.5 + 62 +256 + 6 +BYLAYER + 49 +-0.5 + 62 +256 + 6 +BYLAYER + 0 +MLEADERSTYLE + 5 +2C +102 +{ACAD_REACTORS +330 +F +102 +} +330 +F +100 +AcDbMLeaderStyle +179 +2 +170 +2 +171 +1 +172 +0 + 90 +2 + 40 +0.0 + 41 +0.0 +173 +1 + 91 +-1056964608 + 92 +-2 +290 +1 + 42 +2.0 +291 +1 + 43 +8.0 + 3 +Standard + 44 +4.0 +300 + +342 +29 +174 +1 +175 +1 +176 +0 +178 +1 + 93 +-1056964608 + 45 +4.0 +292 +0 +297 +0 + 46 +4.0 + 94 +-1056964608 + 47 +1.0 + 49 +1.0 +140 +1.0 +294 +1 +141 +0.0 +177 +0 +142 +1.0 +295 +0 +296 +0 +143 +3.75 +271 +0 +272 +9 +273 +9 + 0 +DICTIONARY + 5 +2D +330 +A +100 +AcDbDictionary +280 +1 +281 +1 + 3 +CREATED_BY_EZDXF +350 +2E + 3 +WRITTEN_BY_EZDXF +350 +32 + 0 +DICTIONARYVAR + 5 +2E +330 +2D +100 +DictionaryVariables +280 +0 + 1 +1.4.3 @ 2025-11-09T21:03:02.317097+00:00 + 0 +DICTIONARYVAR + 5 +32 +330 +2D +100 +DictionaryVariables +280 +0 + 1 +1.4.3 @ 2025-11-09T21:03:02.322017+00:00 + 0 +ENDSEC + 0 +EOF diff --git a/docs/assets/dxf/induction_demo/materials.dxf b/docs/assets/dxf/induction_demo/materials.dxf new file mode 100644 index 0000000..a806d16 --- /dev/null +++ b/docs/assets/dxf/induction_demo/materials.dxf @@ -0,0 +1,3760 @@ + 0 +SECTION + 2 +HEADER + 9 +$ACADVER + 1 +AC1024 + 9 +$ACADMAINTVER + 70 +6 + 9 +$DWGCODEPAGE + 3 +ANSI_1252 + 9 +$LASTSAVEDBY + 1 +ezdxf + 9 +$INSBASE + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$EXTMIN + 10 +1e+20 + 20 +1e+20 + 30 +1e+20 + 9 +$EXTMAX + 10 +-1e+20 + 20 +-1e+20 + 30 +-1e+20 + 9 +$LIMMIN + 10 +0.0 + 20 +0.0 + 9 +$LIMMAX + 10 +420.0 + 20 +297.0 + 9 +$ORTHOMODE + 70 +0 + 9 +$REGENMODE + 70 +1 + 9 +$FILLMODE + 70 +1 + 9 +$QTEXTMODE + 70 +0 + 9 +$MIRRTEXT + 70 +1 + 9 +$LTSCALE + 40 +1.0 + 9 +$ATTMODE + 70 +1 + 9 +$TEXTSIZE + 40 +2.5 + 9 +$TRACEWID + 40 +1.0 + 9 +$TEXTSTYLE + 7 +Standard + 9 +$CLAYER + 8 +0 + 9 +$CELTYPE + 6 +ByLayer + 9 +$CECOLOR + 62 +256 + 9 +$CELTSCALE + 40 +1.0 + 9 +$DISPSILH + 70 +0 + 9 +$DIMSCALE + 40 +1.0 + 9 +$DIMASZ + 40 +2.5 + 9 +$DIMEXO + 40 +0.625 + 9 +$DIMDLI + 40 +3.75 + 9 +$DIMRND + 40 +0.0 + 9 +$DIMDLE + 40 +0.0 + 9 +$DIMEXE + 40 +1.25 + 9 +$DIMTP + 40 +0.0 + 9 +$DIMTM + 40 +0.0 + 9 +$DIMTXT + 40 +2.5 + 9 +$DIMCEN + 40 +2.5 + 9 +$DIMTSZ + 40 +0.0 + 9 +$DIMTOL + 70 +0 + 9 +$DIMLIM + 70 +0 + 9 +$DIMTIH + 70 +0 + 9 +$DIMTOH + 70 +0 + 9 +$DIMSE1 + 70 +0 + 9 +$DIMSE2 + 70 +0 + 9 +$DIMTAD + 70 +1 + 9 +$DIMZIN + 70 +8 + 9 +$DIMBLK + 1 + + 9 +$DIMASO + 70 +1 + 9 +$DIMSHO + 70 +1 + 9 +$DIMPOST + 1 + + 9 +$DIMAPOST + 1 + + 9 +$DIMALT + 70 +0 + 9 +$DIMALTD + 70 +3 + 9 +$DIMALTF + 40 +0.03937007874 + 9 +$DIMLFAC + 40 +1.0 + 9 +$DIMTOFL + 70 +1 + 9 +$DIMTVP + 40 +0.0 + 9 +$DIMTIX + 70 +0 + 9 +$DIMSOXD + 70 +0 + 9 +$DIMSAH + 70 +0 + 9 +$DIMBLK1 + 1 + + 9 +$DIMBLK2 + 1 + + 9 +$DIMSTYLE + 2 +ISO-25 + 9 +$DIMCLRD + 70 +0 + 9 +$DIMCLRE + 70 +0 + 9 +$DIMCLRT + 70 +0 + 9 +$DIMTFAC + 40 +1.0 + 9 +$DIMGAP + 40 +0.625 + 9 +$DIMJUST + 70 +0 + 9 +$DIMSD1 + 70 +0 + 9 +$DIMSD2 + 70 +0 + 9 +$DIMTOLJ + 70 +0 + 9 +$DIMTZIN + 70 +8 + 9 +$DIMALTZ + 70 +0 + 9 +$DIMALTTZ + 70 +0 + 9 +$DIMUPT + 70 +0 + 9 +$DIMDEC + 70 +2 + 9 +$DIMTDEC + 70 +2 + 9 +$DIMALTU + 70 +2 + 9 +$DIMALTTD + 70 +3 + 9 +$DIMTXSTY + 7 +Standard + 9 +$DIMAUNIT + 70 +0 + 9 +$DIMADEC + 70 +0 + 9 +$DIMALTRND + 40 +0.0 + 9 +$DIMAZIN + 70 +0 + 9 +$DIMDSEP + 70 +44 + 9 +$DIMATFIT + 70 +3 + 9 +$DIMFRAC + 70 +0 + 9 +$DIMLDRBLK + 1 + + 9 +$DIMLUNIT + 70 +2 + 9 +$DIMLWD + 70 +-2 + 9 +$DIMLWE + 70 +-2 + 9 +$DIMTMOVE + 70 +0 + 9 +$DIMFXL + 40 +1.0 + 9 +$DIMFXLON + 70 +0 + 9 +$DIMJOGANG + 40 +0.785398163397 + 9 +$DIMTFILL + 70 +0 + 9 +$DIMTFILLCLR + 70 +0 + 9 +$DIMARCSYM + 70 +0 + 9 +$DIMLTYPE + 6 + + 9 +$DIMLTEX1 + 6 + + 9 +$DIMLTEX2 + 6 + + 9 +$DIMTXTDIRECTION + 70 +0 + 9 +$LUNITS + 70 +2 + 9 +$LUPREC + 70 +4 + 9 +$SKETCHINC + 40 +1.0 + 9 +$FILLETRAD + 40 +10.0 + 9 +$AUNITS + 70 +0 + 9 +$AUPREC + 70 +2 + 9 +$MENU + 1 +. + 9 +$ELEVATION + 40 +0.0 + 9 +$PELEVATION + 40 +0.0 + 9 +$THICKNESS + 40 +0.0 + 9 +$LIMCHECK + 70 +0 + 9 +$CHAMFERA + 40 +0.0 + 9 +$CHAMFERB + 40 +0.0 + 9 +$CHAMFERC + 40 +0.0 + 9 +$CHAMFERD + 40 +0.0 + 9 +$SKPOLY + 70 +0 + 9 +$TDCREATE + 40 +2460989.8771064817 + 9 +$TDUCREATE + 40 +2458532.153996898 + 9 +$TDUPDATE + 40 +2460989.8771064817 + 9 +$TDUUPDATE + 40 +2458532.1544311 + 9 +$TDINDWG + 40 +0.0 + 9 +$TDUSRTIMER + 40 +0.0 + 9 +$USRTIMER + 70 +1 + 9 +$ANGBASE + 50 +0.0 + 9 +$ANGDIR + 70 +0 + 9 +$PDMODE + 70 +0 + 9 +$PDSIZE + 40 +0.0 + 9 +$PLINEWID + 40 +0.0 + 9 +$SPLFRAME + 70 +0 + 9 +$SPLINETYPE + 70 +6 + 9 +$SPLINESEGS + 70 +8 + 9 +$HANDSEED + 5 +41 + 9 +$SURFTAB1 + 70 +6 + 9 +$SURFTAB2 + 70 +6 + 9 +$SURFTYPE + 70 +6 + 9 +$SURFU + 70 +6 + 9 +$SURFV + 70 +6 + 9 +$UCSBASE + 2 + + 9 +$UCSNAME + 2 + + 9 +$UCSORG + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$UCSXDIR + 10 +1.0 + 20 +0.0 + 30 +0.0 + 9 +$UCSYDIR + 10 +0.0 + 20 +1.0 + 30 +0.0 + 9 +$UCSORTHOREF + 2 + + 9 +$UCSORTHOVIEW + 70 +0 + 9 +$UCSORGTOP + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$UCSORGBOTTOM + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$UCSORGLEFT + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$UCSORGRIGHT + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$UCSORGFRONT + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$UCSORGBACK + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$PUCSBASE + 2 + + 9 +$PUCSNAME + 2 + + 9 +$PUCSORG + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$PUCSXDIR + 10 +1.0 + 20 +0.0 + 30 +0.0 + 9 +$PUCSYDIR + 10 +0.0 + 20 +1.0 + 30 +0.0 + 9 +$PUCSORTHOREF + 2 + + 9 +$PUCSORTHOVIEW + 70 +0 + 9 +$PUCSORGTOP + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$PUCSORGBOTTOM + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$PUCSORGLEFT + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$PUCSORGRIGHT + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$PUCSORGFRONT + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$PUCSORGBACK + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$USERI1 + 70 +0 + 9 +$USERI2 + 70 +0 + 9 +$USERI3 + 70 +0 + 9 +$USERI4 + 70 +0 + 9 +$USERI5 + 70 +0 + 9 +$USERR1 + 40 +0.0 + 9 +$USERR2 + 40 +0.0 + 9 +$USERR3 + 40 +0.0 + 9 +$USERR4 + 40 +0.0 + 9 +$USERR5 + 40 +0.0 + 9 +$WORLDVIEW + 70 +1 + 9 +$SHADEDGE + 70 +3 + 9 +$SHADEDIF + 70 +70 + 9 +$TILEMODE + 70 +1 + 9 +$MAXACTVP + 70 +64 + 9 +$PINSBASE + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$PLIMCHECK + 70 +0 + 9 +$PEXTMIN + 10 +1e+20 + 20 +1e+20 + 30 +1e+20 + 9 +$PEXTMAX + 10 +-1e+20 + 20 +-1e+20 + 30 +-1e+20 + 9 +$PLIMMIN + 10 +0.0 + 20 +0.0 + 9 +$PLIMMAX + 10 +420.0 + 20 +297.0 + 9 +$UNITMODE + 70 +0 + 9 +$VISRETAIN + 70 +1 + 9 +$PLINEGEN + 70 +0 + 9 +$PSLTSCALE + 70 +1 + 9 +$TREEDEPTH + 70 +3020 + 9 +$CMLSTYLE + 2 +Standard + 9 +$CMLJUST + 70 +0 + 9 +$CMLSCALE + 40 +20.0 + 9 +$PROXYGRAPHICS + 70 +1 + 9 +$MEASUREMENT + 70 +1 + 9 +$CELWEIGHT +370 +-1 + 9 +$ENDCAPS +280 +0 + 9 +$JOINSTYLE +280 +0 + 9 +$LWDISPLAY +290 +0 + 9 +$INSUNITS + 70 +6 + 9 +$HYPERLINKBASE + 1 + + 9 +$STYLESHEET + 1 + + 9 +$XEDIT +290 +1 + 9 +$CEPSNTYPE +380 +0 + 9 +$PSTYLEMODE +290 +1 + 9 +$FINGERPRINTGUID + 2 +{49CCA8DC-8041-40D0-8768-EEC63E14F008} + 9 +$VERSIONGUID + 2 +{6D2D62DC-FFFE-454A-A9F2-06900A0D89B9} + 9 +$EXTNAMES +290 +1 + 9 +$PSVPSCALE + 40 +0.0 + 9 +$OLESTARTUP +290 +0 + 9 +$SORTENTS +280 +127 + 9 +$INDEXCTL +280 +0 + 9 +$HIDETEXT +280 +1 + 9 +$XCLIPFRAME +280 +1 + 9 +$HALOGAP +280 +0 + 9 +$OBSCOLOR + 70 +257 + 9 +$OBSLTYPE +280 +0 + 9 +$INTERSECTIONDISPLAY +280 +0 + 9 +$INTERSECTIONCOLOR + 70 +257 + 9 +$DIMASSOC +280 +2 + 9 +$PROJECTNAME + 1 + + 9 +$CAMERADISPLAY +290 +0 + 9 +$LENSLENGTH + 40 +50.0 + 9 +$CAMERAHEIGHT + 40 +0.0 + 9 +$STEPSPERSEC + 40 +24.0 + 9 +$STEPSIZE + 40 +100.0 + 9 +$3DDWFPREC + 40 +2.0 + 9 +$PSOLWIDTH + 40 +0.005 + 9 +$PSOLHEIGHT + 40 +0.08 + 9 +$LOFTANG1 + 40 +1.570796326795 + 9 +$LOFTANG2 + 40 +1.570796326795 + 9 +$LOFTMAG1 + 40 +0.0 + 9 +$LOFTMAG2 + 40 +0.0 + 9 +$LOFTPARAM + 70 +7 + 9 +$LOFTNORMALS +280 +1 + 9 +$LATITUDE + 40 +37.795 + 9 +$LONGITUDE + 40 +-122.394 + 9 +$NORTHDIRECTION + 40 +0.0 + 9 +$TIMEZONE + 70 +-8000 + 9 +$LIGHTGLYPHDISPLAY +280 +1 + 9 +$TILEMODELIGHTSYNCH +280 +1 + 9 +$CMATERIAL +347 +20 + 9 +$SOLIDHIST +280 +0 + 9 +$SHOWHIST +280 +1 + 9 +$DWFFRAME +280 +2 + 9 +$DGNFRAME +280 +2 + 9 +$REALWORLDSCALE +290 +1 + 9 +$INTERFERECOLOR + 62 +256 + 9 +$CSHADOW +280 +0 + 9 +$SHADOWPLANELOCATION + 40 +0.0 + 0 +ENDSEC + 0 +SECTION + 2 +CLASSES + 0 +CLASS + 1 +ACDBDICTIONARYWDFLT + 2 +AcDbDictionaryWithDefault + 3 +ObjectDBX Classes + 90 +0 + 91 +0 +280 +0 +281 +0 + 0 +CLASS + 1 +SUN + 2 +AcDbSun + 3 +SCENEOE + 90 +1153 + 91 +0 +280 +0 +281 +0 + 0 +CLASS + 1 +VISUALSTYLE + 2 +AcDbVisualStyle + 3 +ObjectDBX Classes + 90 +4095 + 91 +0 +280 +0 +281 +0 + 0 +CLASS + 1 +MATERIAL + 2 +AcDbMaterial + 3 +ObjectDBX Classes + 90 +1153 + 91 +0 +280 +0 +281 +0 + 0 +CLASS + 1 +SCALE + 2 +AcDbScale + 3 +ObjectDBX Classes + 90 +1153 + 91 +0 +280 +0 +281 +0 + 0 +CLASS + 1 +TABLESTYLE + 2 +AcDbTableStyle + 3 +ObjectDBX Classes + 90 +4095 + 91 +0 +280 +0 +281 +0 + 0 +CLASS + 1 +MLEADERSTYLE + 2 +AcDbMLeaderStyle + 3 +ACDB_MLEADERSTYLE_CLASS + 90 +4095 + 91 +0 +280 +0 +281 +0 + 0 +CLASS + 1 +DICTIONARYVAR + 2 +AcDbDictionaryVar + 3 +ObjectDBX Classes + 90 +0 + 91 +0 +280 +0 +281 +0 + 0 +CLASS + 1 +CELLSTYLEMAP + 2 +AcDbCellStyleMap + 3 +ObjectDBX Classes + 90 +1152 + 91 +0 +280 +0 +281 +0 + 0 +CLASS + 1 +MENTALRAYRENDERSETTINGS + 2 +AcDbMentalRayRenderSettings + 3 +SCENEOE + 90 +1024 + 91 +0 +280 +0 +281 +0 + 0 +CLASS + 1 +ACDBDETAILVIEWSTYLE + 2 +AcDbDetailViewStyle + 3 +ObjectDBX Classes + 90 +1025 + 91 +0 +280 +0 +281 +0 + 0 +CLASS + 1 +ACDBSECTIONVIEWSTYLE + 2 +AcDbSectionViewStyle + 3 +ObjectDBX Classes + 90 +1025 + 91 +0 +280 +0 +281 +0 + 0 +CLASS + 1 +RASTERVARIABLES + 2 +AcDbRasterVariables + 3 +ISM + 90 +0 + 91 +0 +280 +0 +281 +0 + 0 +CLASS + 1 +ACDBPLACEHOLDER + 2 +AcDbPlaceHolder + 3 +ObjectDBX Classes + 90 +0 + 91 +0 +280 +0 +281 +0 + 0 +CLASS + 1 +LAYOUT + 2 +AcDbLayout + 3 +ObjectDBX Classes + 90 +0 + 91 +0 +280 +0 +281 +0 + 0 +ENDSEC + 0 +SECTION + 2 +TABLES + 0 +TABLE + 2 +VPORT + 5 +8 +330 +0 +100 +AcDbSymbolTable + 70 +1 + 0 +VPORT + 5 +23 +330 +8 +100 +AcDbSymbolTableRecord +100 +AcDbViewportTableRecord + 2 +*Active + 70 +0 + 10 +0.0 + 20 +0.0 + 11 +1.0 + 21 +1.0 + 12 +0.0 + 22 +0.0 + 13 +0.0 + 23 +0.0 + 14 +0.5 + 24 +0.5 + 15 +0.5 + 25 +0.5 + 16 +0.0 + 26 +0.0 + 36 +1.0 + 17 +0.0 + 27 +0.0 + 37 +0.0 + 40 +1000.0 + 41 +1.34 + 42 +50.0 + 43 +0.0 + 44 +0.0 + 50 +0.0 + 51 +0.0 + 71 +0 + 72 +1000 + 73 +1 + 74 +3 + 75 +0 + 76 +0 + 77 +0 + 78 +0 +281 +0 + 65 +0 +146 +0.0 + 0 +ENDTAB + 0 +TABLE + 2 +LTYPE + 5 +2 +330 +0 +100 +AcDbSymbolTable + 70 +3 + 0 +LTYPE + 5 +24 +330 +2 +100 +AcDbSymbolTableRecord +100 +AcDbLinetypeTableRecord + 2 +ByBlock + 70 +0 + 3 + + 72 +65 + 73 +0 + 40 +0.0 + 0 +LTYPE + 5 +25 +330 +2 +100 +AcDbSymbolTableRecord +100 +AcDbLinetypeTableRecord + 2 +ByLayer + 70 +0 + 3 + + 72 +65 + 73 +0 + 40 +0.0 + 0 +LTYPE + 5 +26 +330 +2 +100 +AcDbSymbolTableRecord +100 +AcDbLinetypeTableRecord + 2 +Continuous + 70 +0 + 3 + + 72 +65 + 73 +0 + 40 +0.0 + 0 +ENDTAB + 0 +TABLE + 2 +LAYER + 5 +1 +330 +0 +100 +AcDbSymbolTable + 70 +2 + 0 +LAYER + 5 +27 +330 +1 +100 +AcDbSymbolTableRecord +100 +AcDbLayerTableRecord + 2 +0 + 70 +0 + 62 +7 + 6 +Continuous +370 +-3 +390 +13 +347 +21 + 0 +LAYER + 5 +28 +330 +1 +100 +AcDbSymbolTableRecord +100 +AcDbLayerTableRecord + 2 +Defpoints + 70 +0 + 62 +7 + 6 +Continuous +290 +0 +370 +-3 +390 +13 +347 +21 + 0 +ENDTAB + 0 +TABLE + 2 +STYLE + 5 +5 +330 +0 +100 +AcDbSymbolTable + 70 +1 + 0 +STYLE + 5 +29 +330 +5 +100 +AcDbSymbolTableRecord +100 +AcDbTextStyleTableRecord + 2 +Standard + 70 +0 + 40 +0.0 + 41 +1.0 + 50 +0.0 + 71 +0 + 42 +2.5 + 3 +txt + 4 + + 0 +ENDTAB + 0 +TABLE + 2 +VIEW + 5 +7 +330 +0 +100 +AcDbSymbolTable + 70 +0 + 0 +ENDTAB + 0 +TABLE + 2 +UCS + 5 +6 +330 +0 +100 +AcDbSymbolTable + 70 +0 + 0 +ENDTAB + 0 +TABLE + 2 +APPID + 5 +3 +330 +0 +100 +AcDbSymbolTable + 70 +3 + 0 +APPID + 5 +2A +330 +3 +100 +AcDbSymbolTableRecord +100 +AcDbRegAppTableRecord + 2 +ACAD + 70 +0 + 0 +APPID + 5 +3E +330 +3 +100 +AcDbSymbolTableRecord +100 +AcDbRegAppTableRecord + 2 +HATCHBACKGROUNDCOLOR + 70 +0 + 0 +APPID + 5 +3F +330 +3 +100 +AcDbSymbolTableRecord +100 +AcDbRegAppTableRecord + 2 +EZDXF + 70 +0 + 0 +ENDTAB + 0 +TABLE + 2 +DIMSTYLE + 5 +4 +330 +0 +100 +AcDbSymbolTable + 70 +1 +100 +AcDbDimStyleTable + 0 +DIMSTYLE +105 +2B +330 +4 +100 +AcDbSymbolTableRecord +100 +AcDbDimStyleTableRecord + 2 +Standard + 70 +0 + 40 +1.0 + 41 +2.5 + 42 +0.625 + 43 +3.75 + 44 +1.25 + 45 +0.0 + 46 +0.0 + 47 +0.0 + 48 +0.0 + 49 +2.5 +140 +2.5 +141 +2.5 +142 +0.0 +143 +0.03937007874 +144 +1.0 +145 +0.0 +146 +1.0 +147 +0.625 +148 +0.0 + 69 +0 + 70 +0 + 71 +0 + 72 +0 + 73 +0 + 74 +0 + 75 +0 + 76 +0 + 77 +1 + 78 +8 + 79 +3 +170 +0 +171 +3 +172 +1 +173 +0 +174 +0 +175 +0 +176 +0 +177 +0 +178 +0 +179 +2 +271 +2 +272 +2 +273 +2 +274 +3 +275 +0 +276 +0 +277 +2 +278 +44 +279 +0 +280 +0 +281 +0 +282 +0 +283 +0 +284 +8 +285 +0 +286 +0 +288 +0 +289 +3 +290 +0 +371 +-2 +372 +-2 + 0 +ENDTAB + 0 +TABLE + 2 +BLOCK_RECORD + 5 +9 +330 +0 +100 +AcDbSymbolTable + 70 +2 + 0 +BLOCK_RECORD + 5 +17 +330 +9 +100 +AcDbSymbolTableRecord +100 +AcDbBlockTableRecord + 2 +*Model_Space +340 +1A + 70 +0 +280 +1 +281 +0 + 0 +BLOCK_RECORD + 5 +1B +330 +9 +100 +AcDbSymbolTableRecord +100 +AcDbBlockTableRecord + 2 +*Paper_Space +340 +1E + 70 +0 +280 +1 +281 +0 + 0 +ENDTAB + 0 +ENDSEC + 0 +SECTION + 2 +BLOCKS + 0 +BLOCK + 5 +18 +330 +17 +100 +AcDbEntity + 8 +0 +100 +AcDbBlockBegin + 2 +*Model_Space + 70 +0 + 10 +0.0 + 20 +0.0 + 30 +0.0 + 3 +*Model_Space + 1 + + 0 +ENDBLK + 5 +19 +330 +17 +100 +AcDbEntity + 8 +0 +100 +AcDbBlockEnd + 0 +BLOCK + 5 +1C +330 +1B +100 +AcDbEntity + 8 +0 +100 +AcDbBlockBegin + 2 +*Paper_Space + 70 +0 + 10 +0.0 + 20 +0.0 + 30 +0.0 + 3 +*Paper_Space + 1 + + 0 +ENDBLK + 5 +1D +330 +1B +100 +AcDbEntity + 8 +0 +100 +AcDbBlockEnd + 0 +ENDSEC + 0 +SECTION + 2 +ENTITIES + 0 +LWPOLYLINE + 5 +2F +330 +17 +100 +AcDbEntity + 8 +material_stator_steel +100 +AcDbPolyline + 90 +25 + 70 +0 + 10 +0.055 + 20 +0.0 + 10 +0.0531 + 20 +0.0142 + 10 +0.0476 + 20 +0.0275 + 10 +0.0389 + 20 +0.0389 + 10 +0.0275 + 20 +0.0476 + 10 +0.0142 + 20 +0.0531 + 10 +0.0 + 20 +0.055 + 10 +-0.0142 + 20 +0.0531 + 10 +-0.0275 + 20 +0.0476 + 10 +-0.0389 + 20 +0.0389 + 10 +-0.0476 + 20 +0.0275 + 10 +-0.0531 + 20 +0.0142 + 10 +-0.055 + 20 +0.0 + 10 +-0.0531 + 20 +-0.0142 + 10 +-0.0476 + 20 +-0.0275 + 10 +-0.0389 + 20 +-0.0389 + 10 +-0.0275 + 20 +-0.0476 + 10 +-0.0142 + 20 +-0.0531 + 10 +0.0 + 20 +-0.055 + 10 +0.0142 + 20 +-0.0531 + 10 +0.0275 + 20 +-0.0476 + 10 +0.0389 + 20 +-0.0389 + 10 +0.0476 + 20 +-0.0275 + 10 +0.0531 + 20 +-0.0142 + 10 +0.055 + 20 +0.0 + 0 +LWPOLYLINE + 5 +30 +330 +17 +100 +AcDbEntity + 8 +material_air +100 +AcDbPolyline + 90 +5 + 70 +0 + 10 +0.0395 + 20 +-0.0063 + 10 +0.0494 + 20 +-0.0078 + 10 +0.0494 + 20 +0.0078 + 10 +0.0395 + 20 +0.0063 + 10 +0.0395 + 20 +-0.0063 + 0 +LWPOLYLINE + 5 +31 +330 +17 +100 +AcDbEntity + 8 +material_air +100 +AcDbPolyline + 90 +5 + 70 +0 + 10 +0.0252 + 20 +0.0311 + 10 +0.0315 + 20 +0.0389 + 10 +0.0179 + 20 +0.0467 + 10 +0.0143 + 20 +0.0373 + 10 +0.0252 + 20 +0.0311 + 0 +LWPOLYLINE + 5 +32 +330 +17 +100 +AcDbEntity + 8 +material_air +100 +AcDbPolyline + 90 +5 + 70 +0 + 10 +-0.0143 + 20 +0.0373 + 10 +-0.0179 + 20 +0.0467 + 10 +-0.0315 + 20 +0.0389 + 10 +-0.0252 + 20 +0.0311 + 10 +-0.0143 + 20 +0.0373 + 0 +LWPOLYLINE + 5 +33 +330 +17 +100 +AcDbEntity + 8 +material_air +100 +AcDbPolyline + 90 +5 + 70 +0 + 10 +-0.0395 + 20 +0.0063 + 10 +-0.0494 + 20 +0.0078 + 10 +-0.0494 + 20 +-0.0078 + 10 +-0.0395 + 20 +-0.0063 + 10 +-0.0395 + 20 +0.0063 + 0 +LWPOLYLINE + 5 +34 +330 +17 +100 +AcDbEntity + 8 +material_air +100 +AcDbPolyline + 90 +5 + 70 +0 + 10 +-0.0252 + 20 +-0.0311 + 10 +-0.0315 + 20 +-0.0389 + 10 +-0.0179 + 20 +-0.0467 + 10 +-0.0143 + 20 +-0.0373 + 10 +-0.0252 + 20 +-0.0311 + 0 +LWPOLYLINE + 5 +35 +330 +17 +100 +AcDbEntity + 8 +material_air +100 +AcDbPolyline + 90 +5 + 70 +0 + 10 +0.0143 + 20 +-0.0373 + 10 +0.0179 + 20 +-0.0467 + 10 +0.0315 + 20 +-0.0389 + 10 +0.0252 + 20 +-0.0311 + 10 +0.0143 + 20 +-0.0373 + 0 +LWPOLYLINE + 5 +36 +330 +17 +100 +AcDbEntity + 8 +material_air +100 +AcDbPolyline + 90 +19 + 70 +0 + 10 +0.04 + 20 +0.0 + 10 +0.0376 + 20 +0.0137 + 10 +0.0306 + 20 +0.0257 + 10 +0.02 + 20 +0.0346 + 10 +0.0069 + 20 +0.0394 + 10 +-0.0069 + 20 +0.0394 + 10 +-0.02 + 20 +0.0346 + 10 +-0.0306 + 20 +0.0257 + 10 +-0.0376 + 20 +0.0137 + 10 +-0.04 + 20 +0.0 + 10 +-0.0376 + 20 +-0.0137 + 10 +-0.0306 + 20 +-0.0257 + 10 +-0.02 + 20 +-0.0346 + 10 +-0.0069 + 20 +-0.0394 + 10 +0.0069 + 20 +-0.0394 + 10 +0.02 + 20 +-0.0346 + 10 +0.0306 + 20 +-0.0257 + 10 +0.0376 + 20 +-0.0137 + 10 +0.04 + 20 +0.0 + 0 +LWPOLYLINE + 5 +37 +330 +17 +100 +AcDbEntity + 8 +material_rotor_core +100 +AcDbPolyline + 90 +15 + 70 +0 + 10 +0.0232 + 20 +0.0 + 10 +0.0209 + 20 +0.0101 + 10 +0.0145 + 20 +0.0181 + 10 +0.0052 + 20 +0.0226 + 10 +-0.0052 + 20 +0.0226 + 10 +-0.0145 + 20 +0.0181 + 10 +-0.0209 + 20 +0.0101 + 10 +-0.0232 + 20 +0.0 + 10 +-0.0209 + 20 +-0.0101 + 10 +-0.0145 + 20 +-0.0181 + 10 +-0.0052 + 20 +-0.0226 + 10 +0.0052 + 20 +-0.0226 + 10 +0.0145 + 20 +-0.0181 + 10 +0.0209 + 20 +-0.0101 + 10 +0.0232 + 20 +0.0 + 0 +LWPOLYLINE + 5 +38 +330 +17 +100 +AcDbEntity + 8 +material_rotor_bar +100 +AcDbPolyline + 90 +5 + 70 +0 + 10 +0.0081 + 20 +-0.0008 + 10 +0.0226 + 20 +-0.0024 + 10 +0.0226 + 20 +0.0024 + 10 +0.0081 + 20 +0.0008 + 10 +0.0081 + 20 +-0.0008 + 0 +LWPOLYLINE + 5 +39 +330 +17 +100 +AcDbEntity + 8 +material_rotor_bar +100 +AcDbPolyline + 90 +5 + 70 +0 + 10 +0.0048 + 20 +0.0066 + 10 +0.0134 + 20 +0.0184 + 10 +0.0092 + 20 +0.0208 + 10 +0.0033 + 20 +0.0074 + 10 +0.0048 + 20 +0.0066 + 0 +LWPOLYLINE + 5 +3A +330 +17 +100 +AcDbEntity + 8 +material_rotor_bar +100 +AcDbPolyline + 90 +5 + 70 +0 + 10 +-0.0033 + 20 +0.0074 + 10 +-0.0092 + 20 +0.0208 + 10 +-0.0134 + 20 +0.0184 + 10 +-0.0048 + 20 +0.0066 + 10 +-0.0033 + 20 +0.0074 + 0 +LWPOLYLINE + 5 +3B +330 +17 +100 +AcDbEntity + 8 +material_rotor_bar +100 +AcDbPolyline + 90 +5 + 70 +0 + 10 +-0.0081 + 20 +0.0008 + 10 +-0.0226 + 20 +0.0024 + 10 +-0.0226 + 20 +-0.0024 + 10 +-0.0081 + 20 +-0.0008 + 10 +-0.0081 + 20 +0.0008 + 0 +LWPOLYLINE + 5 +3C +330 +17 +100 +AcDbEntity + 8 +material_rotor_bar +100 +AcDbPolyline + 90 +5 + 70 +0 + 10 +-0.0048 + 20 +-0.0066 + 10 +-0.0134 + 20 +-0.0184 + 10 +-0.0092 + 20 +-0.0208 + 10 +-0.0033 + 20 +-0.0074 + 10 +-0.0048 + 20 +-0.0066 + 0 +LWPOLYLINE + 5 +3D +330 +17 +100 +AcDbEntity + 8 +material_rotor_bar +100 +AcDbPolyline + 90 +5 + 70 +0 + 10 +0.0033 + 20 +-0.0074 + 10 +0.0092 + 20 +-0.0208 + 10 +0.0134 + 20 +-0.0184 + 10 +0.0048 + 20 +-0.0066 + 10 +0.0033 + 20 +-0.0074 + 0 +ENDSEC + 0 +SECTION + 2 +OBJECTS + 0 +DICTIONARY + 5 +A +330 +0 +100 +AcDbDictionary +281 +1 + 3 +ACAD_COLOR +350 +B + 3 +ACAD_GROUP +350 +C + 3 +ACAD_LAYOUT +350 +D + 3 +ACAD_MATERIAL +350 +E + 3 +ACAD_MLEADERSTYLE +350 +F + 3 +ACAD_MLINESTYLE +350 +10 + 3 +ACAD_PLOTSETTINGS +350 +11 + 3 +ACAD_PLOTSTYLENAME +350 +12 + 3 +ACAD_SCALELIST +350 +14 + 3 +ACAD_TABLESTYLE +350 +15 + 3 +ACAD_VISUALSTYLE +350 +16 + 3 +EZDXF_META +350 +2D + 0 +DICTIONARY + 5 +B +330 +A +100 +AcDbDictionary +281 +1 + 0 +DICTIONARY + 5 +C +330 +A +100 +AcDbDictionary +281 +1 + 0 +DICTIONARY + 5 +D +330 +A +100 +AcDbDictionary +281 +1 + 3 +Model +350 +1A + 3 +Layout1 +350 +1E + 0 +DICTIONARY + 5 +E +330 +A +100 +AcDbDictionary +281 +1 + 3 +ByBlock +350 +1F + 3 +ByLayer +350 +20 + 3 +Global +350 +21 + 0 +DICTIONARY + 5 +F +330 +A +100 +AcDbDictionary +281 +1 + 3 +Standard +350 +2C + 0 +DICTIONARY + 5 +10 +330 +A +100 +AcDbDictionary +281 +1 + 3 +Standard +350 +22 + 0 +DICTIONARY + 5 +11 +330 +A +100 +AcDbDictionary +281 +1 + 0 +ACDBDICTIONARYWDFLT + 5 +12 +330 +A +100 +AcDbDictionary +281 +1 + 3 +Normal +350 +13 +100 +AcDbDictionaryWithDefault +340 +13 + 0 +ACDBPLACEHOLDER + 5 +13 +330 +12 + 0 +DICTIONARY + 5 +14 +330 +A +100 +AcDbDictionary +281 +1 + 0 +DICTIONARY + 5 +15 +330 +A +100 +AcDbDictionary +281 +1 + 0 +DICTIONARY + 5 +16 +330 +A +100 +AcDbDictionary +281 +1 + 0 +LAYOUT + 5 +1A +330 +D +100 +AcDbPlotSettings + 1 + + 4 +A3 + 6 + + 40 +7.5 + 41 +20.0 + 42 +7.5 + 43 +20.0 + 44 +420.0 + 45 +297.0 + 46 +0.0 + 47 +0.0 + 48 +0.0 + 49 +0.0 +140 +0.0 +141 +0.0 +142 +1.0 +143 +1.0 + 70 +1024 + 72 +1 + 73 +0 + 74 +5 + 7 + + 75 +16 + 76 +0 + 77 +2 + 78 +300 +147 +1.0 +148 +0.0 +149 +0.0 +100 +AcDbLayout + 1 +Model + 70 +1 + 71 +0 + 10 +0.0 + 20 +0.0 + 11 +420.0 + 21 +297.0 + 12 +0.0 + 22 +0.0 + 32 +0.0 + 14 +1e+20 + 24 +1e+20 + 34 +1e+20 + 15 +-1e+20 + 25 +-1e+20 + 35 +-1e+20 +146 +0.0 + 13 +0.0 + 23 +0.0 + 33 +0.0 + 16 +1.0 + 26 +0.0 + 36 +0.0 + 17 +0.0 + 27 +1.0 + 37 +0.0 + 76 +1 +330 +17 + 0 +LAYOUT + 5 +1E +330 +D +100 +AcDbPlotSettings + 1 + + 4 +A3 + 6 + + 40 +7.5 + 41 +20.0 + 42 +7.5 + 43 +20.0 + 44 +420.0 + 45 +297.0 + 46 +0.0 + 47 +0.0 + 48 +0.0 + 49 +0.0 +140 +0.0 +141 +0.0 +142 +1.0 +143 +1.0 + 70 +0 + 72 +1 + 73 +0 + 74 +5 + 7 + + 75 +16 + 76 +0 + 77 +2 + 78 +300 +147 +1.0 +148 +0.0 +149 +0.0 +100 +AcDbLayout + 1 +Layout1 + 70 +1 + 71 +1 + 10 +0.0 + 20 +0.0 + 11 +420.0 + 21 +297.0 + 12 +0.0 + 22 +0.0 + 32 +0.0 + 14 +1e+20 + 24 +1e+20 + 34 +1e+20 + 15 +-1e+20 + 25 +-1e+20 + 35 +-1e+20 +146 +0.0 + 13 +0.0 + 23 +0.0 + 33 +0.0 + 16 +1.0 + 26 +0.0 + 36 +0.0 + 17 +0.0 + 27 +1.0 + 37 +0.0 + 76 +1 +330 +1B + 0 +MATERIAL + 5 +1F +102 +{ACAD_REACTORS +330 +E +102 +} +330 +E +100 +AcDbMaterial + 1 +ByBlock + 2 + + 70 +0 + 40 +1.0 + 71 +1 + 41 +1.0 + 91 +-1023410177 + 42 +1.0 + 72 +1 + 3 + + 73 +1 + 74 +1 + 75 +1 + 44 +0.5 + 73 +0 + 45 +1.0 + 46 +1.0 + 77 +1 + 4 + + 78 +1 + 79 +1 +170 +1 + 48 +1.0 +171 +1 + 6 + +172 +1 +173 +1 +174 +1 +140 +1.0 +141 +1.0 +175 +1 + 7 + +176 +1 +177 +1 +178 +1 +143 +1.0 +179 +1 + 8 + +270 +1 +271 +1 +272 +1 +145 +1.0 +146 +1.0 +273 +1 + 9 + +274 +1 +275 +1 +276 +1 + 42 +1.0 + 72 +1 + 3 + + 73 +1 + 74 +1 + 75 +1 + 94 +63 + 0 +MATERIAL + 5 +20 +102 +{ACAD_REACTORS +330 +E +102 +} +330 +E +100 +AcDbMaterial + 1 +ByLayer + 2 + + 70 +0 + 40 +1.0 + 71 +1 + 41 +1.0 + 91 +-1023410177 + 42 +1.0 + 72 +1 + 3 + + 73 +1 + 74 +1 + 75 +1 + 44 +0.5 + 73 +0 + 45 +1.0 + 46 +1.0 + 77 +1 + 4 + + 78 +1 + 79 +1 +170 +1 + 48 +1.0 +171 +1 + 6 + +172 +1 +173 +1 +174 +1 +140 +1.0 +141 +1.0 +175 +1 + 7 + +176 +1 +177 +1 +178 +1 +143 +1.0 +179 +1 + 8 + +270 +1 +271 +1 +272 +1 +145 +1.0 +146 +1.0 +273 +1 + 9 + +274 +1 +275 +1 +276 +1 + 42 +1.0 + 72 +1 + 3 + + 73 +1 + 74 +1 + 75 +1 + 94 +63 + 0 +MATERIAL + 5 +21 +102 +{ACAD_REACTORS +330 +E +102 +} +330 +E +100 +AcDbMaterial + 1 +Global + 2 + + 70 +0 + 40 +1.0 + 71 +1 + 41 +1.0 + 91 +-1023410177 + 42 +1.0 + 72 +1 + 3 + + 73 +1 + 74 +1 + 75 +1 + 44 +0.5 + 73 +0 + 45 +1.0 + 46 +1.0 + 77 +1 + 4 + + 78 +1 + 79 +1 +170 +1 + 48 +1.0 +171 +1 + 6 + +172 +1 +173 +1 +174 +1 +140 +1.0 +141 +1.0 +175 +1 + 7 + +176 +1 +177 +1 +178 +1 +143 +1.0 +179 +1 + 8 + +270 +1 +271 +1 +272 +1 +145 +1.0 +146 +1.0 +273 +1 + 9 + +274 +1 +275 +1 +276 +1 + 42 +1.0 + 72 +1 + 3 + + 73 +1 + 74 +1 + 75 +1 + 94 +63 + 0 +MLINESTYLE + 5 +22 +102 +{ACAD_REACTORS +330 +10 +102 +} +330 +10 +100 +AcDbMlineStyle + 2 +Standard + 70 +0 + 3 + + 62 +256 + 51 +90.0 + 52 +90.0 + 71 +2 + 49 +0.5 + 62 +256 + 6 +BYLAYER + 49 +-0.5 + 62 +256 + 6 +BYLAYER + 0 +MLEADERSTYLE + 5 +2C +102 +{ACAD_REACTORS +330 +F +102 +} +330 +F +100 +AcDbMLeaderStyle +179 +2 +170 +2 +171 +1 +172 +0 + 90 +2 + 40 +0.0 + 41 +0.0 +173 +1 + 91 +-1056964608 + 92 +-2 +290 +1 + 42 +2.0 +291 +1 + 43 +8.0 + 3 +Standard + 44 +4.0 +300 + +342 +29 +174 +1 +175 +1 +176 +0 +178 +1 + 93 +-1056964608 + 45 +4.0 +292 +0 +297 +0 + 46 +4.0 + 94 +-1056964608 + 47 +1.0 + 49 +1.0 +140 +1.0 +294 +1 +141 +0.0 +177 +0 +142 +1.0 +295 +0 +296 +0 +143 +3.75 +271 +0 +272 +9 +273 +9 + 0 +DICTIONARY + 5 +2D +330 +A +100 +AcDbDictionary +280 +1 +281 +1 + 3 +CREATED_BY_EZDXF +350 +2E + 3 +WRITTEN_BY_EZDXF +350 +40 + 0 +DICTIONARYVAR + 5 +2E +330 +2D +100 +DictionaryVariables +280 +0 + 1 +1.4.3 @ 2025-11-09T21:03:02.343882+00:00 + 0 +DICTIONARYVAR + 5 +40 +330 +2D +100 +DictionaryVariables +280 +0 + 1 +1.4.3 @ 2025-11-09T21:03:02.353736+00:00 + 0 +ENDSEC + 0 +EOF diff --git a/docs/assets/dxf/induction_demo/rotor_bars.dxf b/docs/assets/dxf/induction_demo/rotor_bars.dxf new file mode 100644 index 0000000..f28318a --- /dev/null +++ b/docs/assets/dxf/induction_demo/rotor_bars.dxf @@ -0,0 +1,3260 @@ + 0 +SECTION + 2 +HEADER + 9 +$ACADVER + 1 +AC1024 + 9 +$ACADMAINTVER + 70 +6 + 9 +$DWGCODEPAGE + 3 +ANSI_1252 + 9 +$LASTSAVEDBY + 1 +ezdxf + 9 +$INSBASE + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$EXTMIN + 10 +1e+20 + 20 +1e+20 + 30 +1e+20 + 9 +$EXTMAX + 10 +-1e+20 + 20 +-1e+20 + 30 +-1e+20 + 9 +$LIMMIN + 10 +0.0 + 20 +0.0 + 9 +$LIMMAX + 10 +420.0 + 20 +297.0 + 9 +$ORTHOMODE + 70 +0 + 9 +$REGENMODE + 70 +1 + 9 +$FILLMODE + 70 +1 + 9 +$QTEXTMODE + 70 +0 + 9 +$MIRRTEXT + 70 +1 + 9 +$LTSCALE + 40 +1.0 + 9 +$ATTMODE + 70 +1 + 9 +$TEXTSIZE + 40 +2.5 + 9 +$TRACEWID + 40 +1.0 + 9 +$TEXTSTYLE + 7 +Standard + 9 +$CLAYER + 8 +0 + 9 +$CELTYPE + 6 +ByLayer + 9 +$CECOLOR + 62 +256 + 9 +$CELTSCALE + 40 +1.0 + 9 +$DISPSILH + 70 +0 + 9 +$DIMSCALE + 40 +1.0 + 9 +$DIMASZ + 40 +2.5 + 9 +$DIMEXO + 40 +0.625 + 9 +$DIMDLI + 40 +3.75 + 9 +$DIMRND + 40 +0.0 + 9 +$DIMDLE + 40 +0.0 + 9 +$DIMEXE + 40 +1.25 + 9 +$DIMTP + 40 +0.0 + 9 +$DIMTM + 40 +0.0 + 9 +$DIMTXT + 40 +2.5 + 9 +$DIMCEN + 40 +2.5 + 9 +$DIMTSZ + 40 +0.0 + 9 +$DIMTOL + 70 +0 + 9 +$DIMLIM + 70 +0 + 9 +$DIMTIH + 70 +0 + 9 +$DIMTOH + 70 +0 + 9 +$DIMSE1 + 70 +0 + 9 +$DIMSE2 + 70 +0 + 9 +$DIMTAD + 70 +1 + 9 +$DIMZIN + 70 +8 + 9 +$DIMBLK + 1 + + 9 +$DIMASO + 70 +1 + 9 +$DIMSHO + 70 +1 + 9 +$DIMPOST + 1 + + 9 +$DIMAPOST + 1 + + 9 +$DIMALT + 70 +0 + 9 +$DIMALTD + 70 +3 + 9 +$DIMALTF + 40 +0.03937007874 + 9 +$DIMLFAC + 40 +1.0 + 9 +$DIMTOFL + 70 +1 + 9 +$DIMTVP + 40 +0.0 + 9 +$DIMTIX + 70 +0 + 9 +$DIMSOXD + 70 +0 + 9 +$DIMSAH + 70 +0 + 9 +$DIMBLK1 + 1 + + 9 +$DIMBLK2 + 1 + + 9 +$DIMSTYLE + 2 +ISO-25 + 9 +$DIMCLRD + 70 +0 + 9 +$DIMCLRE + 70 +0 + 9 +$DIMCLRT + 70 +0 + 9 +$DIMTFAC + 40 +1.0 + 9 +$DIMGAP + 40 +0.625 + 9 +$DIMJUST + 70 +0 + 9 +$DIMSD1 + 70 +0 + 9 +$DIMSD2 + 70 +0 + 9 +$DIMTOLJ + 70 +0 + 9 +$DIMTZIN + 70 +8 + 9 +$DIMALTZ + 70 +0 + 9 +$DIMALTTZ + 70 +0 + 9 +$DIMUPT + 70 +0 + 9 +$DIMDEC + 70 +2 + 9 +$DIMTDEC + 70 +2 + 9 +$DIMALTU + 70 +2 + 9 +$DIMALTTD + 70 +3 + 9 +$DIMTXSTY + 7 +Standard + 9 +$DIMAUNIT + 70 +0 + 9 +$DIMADEC + 70 +0 + 9 +$DIMALTRND + 40 +0.0 + 9 +$DIMAZIN + 70 +0 + 9 +$DIMDSEP + 70 +44 + 9 +$DIMATFIT + 70 +3 + 9 +$DIMFRAC + 70 +0 + 9 +$DIMLDRBLK + 1 + + 9 +$DIMLUNIT + 70 +2 + 9 +$DIMLWD + 70 +-2 + 9 +$DIMLWE + 70 +-2 + 9 +$DIMTMOVE + 70 +0 + 9 +$DIMFXL + 40 +1.0 + 9 +$DIMFXLON + 70 +0 + 9 +$DIMJOGANG + 40 +0.785398163397 + 9 +$DIMTFILL + 70 +0 + 9 +$DIMTFILLCLR + 70 +0 + 9 +$DIMARCSYM + 70 +0 + 9 +$DIMLTYPE + 6 + + 9 +$DIMLTEX1 + 6 + + 9 +$DIMLTEX2 + 6 + + 9 +$DIMTXTDIRECTION + 70 +0 + 9 +$LUNITS + 70 +2 + 9 +$LUPREC + 70 +4 + 9 +$SKETCHINC + 40 +1.0 + 9 +$FILLETRAD + 40 +10.0 + 9 +$AUNITS + 70 +0 + 9 +$AUPREC + 70 +2 + 9 +$MENU + 1 +. + 9 +$ELEVATION + 40 +0.0 + 9 +$PELEVATION + 40 +0.0 + 9 +$THICKNESS + 40 +0.0 + 9 +$LIMCHECK + 70 +0 + 9 +$CHAMFERA + 40 +0.0 + 9 +$CHAMFERB + 40 +0.0 + 9 +$CHAMFERC + 40 +0.0 + 9 +$CHAMFERD + 40 +0.0 + 9 +$SKPOLY + 70 +0 + 9 +$TDCREATE + 40 +2460989.8777777776 + 9 +$TDUCREATE + 40 +2458532.153996898 + 9 +$TDUPDATE + 40 +2460989.8777777776 + 9 +$TDUUPDATE + 40 +2458532.1544311 + 9 +$TDINDWG + 40 +0.0 + 9 +$TDUSRTIMER + 40 +0.0 + 9 +$USRTIMER + 70 +1 + 9 +$ANGBASE + 50 +0.0 + 9 +$ANGDIR + 70 +0 + 9 +$PDMODE + 70 +0 + 9 +$PDSIZE + 40 +0.0 + 9 +$PLINEWID + 40 +0.0 + 9 +$SPLFRAME + 70 +0 + 9 +$SPLINETYPE + 70 +6 + 9 +$SPLINESEGS + 70 +8 + 9 +$HANDSEED + 5 +38 + 9 +$SURFTAB1 + 70 +6 + 9 +$SURFTAB2 + 70 +6 + 9 +$SURFTYPE + 70 +6 + 9 +$SURFU + 70 +6 + 9 +$SURFV + 70 +6 + 9 +$UCSBASE + 2 + + 9 +$UCSNAME + 2 + + 9 +$UCSORG + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$UCSXDIR + 10 +1.0 + 20 +0.0 + 30 +0.0 + 9 +$UCSYDIR + 10 +0.0 + 20 +1.0 + 30 +0.0 + 9 +$UCSORTHOREF + 2 + + 9 +$UCSORTHOVIEW + 70 +0 + 9 +$UCSORGTOP + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$UCSORGBOTTOM + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$UCSORGLEFT + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$UCSORGRIGHT + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$UCSORGFRONT + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$UCSORGBACK + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$PUCSBASE + 2 + + 9 +$PUCSNAME + 2 + + 9 +$PUCSORG + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$PUCSXDIR + 10 +1.0 + 20 +0.0 + 30 +0.0 + 9 +$PUCSYDIR + 10 +0.0 + 20 +1.0 + 30 +0.0 + 9 +$PUCSORTHOREF + 2 + + 9 +$PUCSORTHOVIEW + 70 +0 + 9 +$PUCSORGTOP + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$PUCSORGBOTTOM + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$PUCSORGLEFT + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$PUCSORGRIGHT + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$PUCSORGFRONT + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$PUCSORGBACK + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$USERI1 + 70 +0 + 9 +$USERI2 + 70 +0 + 9 +$USERI3 + 70 +0 + 9 +$USERI4 + 70 +0 + 9 +$USERI5 + 70 +0 + 9 +$USERR1 + 40 +0.0 + 9 +$USERR2 + 40 +0.0 + 9 +$USERR3 + 40 +0.0 + 9 +$USERR4 + 40 +0.0 + 9 +$USERR5 + 40 +0.0 + 9 +$WORLDVIEW + 70 +1 + 9 +$SHADEDGE + 70 +3 + 9 +$SHADEDIF + 70 +70 + 9 +$TILEMODE + 70 +1 + 9 +$MAXACTVP + 70 +64 + 9 +$PINSBASE + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$PLIMCHECK + 70 +0 + 9 +$PEXTMIN + 10 +1e+20 + 20 +1e+20 + 30 +1e+20 + 9 +$PEXTMAX + 10 +-1e+20 + 20 +-1e+20 + 30 +-1e+20 + 9 +$PLIMMIN + 10 +0.0 + 20 +0.0 + 9 +$PLIMMAX + 10 +420.0 + 20 +297.0 + 9 +$UNITMODE + 70 +0 + 9 +$VISRETAIN + 70 +1 + 9 +$PLINEGEN + 70 +0 + 9 +$PSLTSCALE + 70 +1 + 9 +$TREEDEPTH + 70 +3020 + 9 +$CMLSTYLE + 2 +Standard + 9 +$CMLJUST + 70 +0 + 9 +$CMLSCALE + 40 +20.0 + 9 +$PROXYGRAPHICS + 70 +1 + 9 +$MEASUREMENT + 70 +1 + 9 +$CELWEIGHT +370 +-1 + 9 +$ENDCAPS +280 +0 + 9 +$JOINSTYLE +280 +0 + 9 +$LWDISPLAY +290 +0 + 9 +$INSUNITS + 70 +6 + 9 +$HYPERLINKBASE + 1 + + 9 +$STYLESHEET + 1 + + 9 +$XEDIT +290 +1 + 9 +$CEPSNTYPE +380 +0 + 9 +$PSTYLEMODE +290 +1 + 9 +$FINGERPRINTGUID + 2 +{F45C26B3-3628-4FD1-BA9B-C1D7C8895113} + 9 +$VERSIONGUID + 2 +{9D6E6FB8-C7E7-4872-8527-2BAAD026CA87} + 9 +$EXTNAMES +290 +1 + 9 +$PSVPSCALE + 40 +0.0 + 9 +$OLESTARTUP +290 +0 + 9 +$SORTENTS +280 +127 + 9 +$INDEXCTL +280 +0 + 9 +$HIDETEXT +280 +1 + 9 +$XCLIPFRAME +280 +1 + 9 +$HALOGAP +280 +0 + 9 +$OBSCOLOR + 70 +257 + 9 +$OBSLTYPE +280 +0 + 9 +$INTERSECTIONDISPLAY +280 +0 + 9 +$INTERSECTIONCOLOR + 70 +257 + 9 +$DIMASSOC +280 +2 + 9 +$PROJECTNAME + 1 + + 9 +$CAMERADISPLAY +290 +0 + 9 +$LENSLENGTH + 40 +50.0 + 9 +$CAMERAHEIGHT + 40 +0.0 + 9 +$STEPSPERSEC + 40 +24.0 + 9 +$STEPSIZE + 40 +100.0 + 9 +$3DDWFPREC + 40 +2.0 + 9 +$PSOLWIDTH + 40 +0.005 + 9 +$PSOLHEIGHT + 40 +0.08 + 9 +$LOFTANG1 + 40 +1.570796326795 + 9 +$LOFTANG2 + 40 +1.570796326795 + 9 +$LOFTMAG1 + 40 +0.0 + 9 +$LOFTMAG2 + 40 +0.0 + 9 +$LOFTPARAM + 70 +7 + 9 +$LOFTNORMALS +280 +1 + 9 +$LATITUDE + 40 +37.795 + 9 +$LONGITUDE + 40 +-122.394 + 9 +$NORTHDIRECTION + 40 +0.0 + 9 +$TIMEZONE + 70 +-8000 + 9 +$LIGHTGLYPHDISPLAY +280 +1 + 9 +$TILEMODELIGHTSYNCH +280 +1 + 9 +$CMATERIAL +347 +20 + 9 +$SOLIDHIST +280 +0 + 9 +$SHOWHIST +280 +1 + 9 +$DWFFRAME +280 +2 + 9 +$DGNFRAME +280 +2 + 9 +$REALWORLDSCALE +290 +1 + 9 +$INTERFERECOLOR + 62 +256 + 9 +$CSHADOW +280 +0 + 9 +$SHADOWPLANELOCATION + 40 +0.0 + 0 +ENDSEC + 0 +SECTION + 2 +CLASSES + 0 +CLASS + 1 +ACDBDICTIONARYWDFLT + 2 +AcDbDictionaryWithDefault + 3 +ObjectDBX Classes + 90 +0 + 91 +0 +280 +0 +281 +0 + 0 +CLASS + 1 +SUN + 2 +AcDbSun + 3 +SCENEOE + 90 +1153 + 91 +0 +280 +0 +281 +0 + 0 +CLASS + 1 +VISUALSTYLE + 2 +AcDbVisualStyle + 3 +ObjectDBX Classes + 90 +4095 + 91 +0 +280 +0 +281 +0 + 0 +CLASS + 1 +MATERIAL + 2 +AcDbMaterial + 3 +ObjectDBX Classes + 90 +1153 + 91 +0 +280 +0 +281 +0 + 0 +CLASS + 1 +SCALE + 2 +AcDbScale + 3 +ObjectDBX Classes + 90 +1153 + 91 +0 +280 +0 +281 +0 + 0 +CLASS + 1 +TABLESTYLE + 2 +AcDbTableStyle + 3 +ObjectDBX Classes + 90 +4095 + 91 +0 +280 +0 +281 +0 + 0 +CLASS + 1 +MLEADERSTYLE + 2 +AcDbMLeaderStyle + 3 +ACDB_MLEADERSTYLE_CLASS + 90 +4095 + 91 +0 +280 +0 +281 +0 + 0 +CLASS + 1 +DICTIONARYVAR + 2 +AcDbDictionaryVar + 3 +ObjectDBX Classes + 90 +0 + 91 +0 +280 +0 +281 +0 + 0 +CLASS + 1 +CELLSTYLEMAP + 2 +AcDbCellStyleMap + 3 +ObjectDBX Classes + 90 +1152 + 91 +0 +280 +0 +281 +0 + 0 +CLASS + 1 +MENTALRAYRENDERSETTINGS + 2 +AcDbMentalRayRenderSettings + 3 +SCENEOE + 90 +1024 + 91 +0 +280 +0 +281 +0 + 0 +CLASS + 1 +ACDBDETAILVIEWSTYLE + 2 +AcDbDetailViewStyle + 3 +ObjectDBX Classes + 90 +1025 + 91 +0 +280 +0 +281 +0 + 0 +CLASS + 1 +ACDBSECTIONVIEWSTYLE + 2 +AcDbSectionViewStyle + 3 +ObjectDBX Classes + 90 +1025 + 91 +0 +280 +0 +281 +0 + 0 +CLASS + 1 +RASTERVARIABLES + 2 +AcDbRasterVariables + 3 +ISM + 90 +0 + 91 +0 +280 +0 +281 +0 + 0 +CLASS + 1 +LAYOUT + 2 +AcDbLayout + 3 +ObjectDBX Classes + 90 +0 + 91 +0 +280 +0 +281 +0 + 0 +CLASS + 1 +ACDBPLACEHOLDER + 2 +AcDbPlaceHolder + 3 +ObjectDBX Classes + 90 +0 + 91 +0 +280 +0 +281 +0 + 0 +ENDSEC + 0 +SECTION + 2 +TABLES + 0 +TABLE + 2 +VPORT + 5 +8 +330 +0 +100 +AcDbSymbolTable + 70 +1 + 0 +VPORT + 5 +23 +330 +8 +100 +AcDbSymbolTableRecord +100 +AcDbViewportTableRecord + 2 +*Active + 70 +0 + 10 +0.0 + 20 +0.0 + 11 +1.0 + 21 +1.0 + 12 +0.0 + 22 +0.0 + 13 +0.0 + 23 +0.0 + 14 +0.5 + 24 +0.5 + 15 +0.5 + 25 +0.5 + 16 +0.0 + 26 +0.0 + 36 +1.0 + 17 +0.0 + 27 +0.0 + 37 +0.0 + 40 +1000.0 + 41 +1.34 + 42 +50.0 + 43 +0.0 + 44 +0.0 + 50 +0.0 + 51 +0.0 + 71 +0 + 72 +1000 + 73 +1 + 74 +3 + 75 +0 + 76 +0 + 77 +0 + 78 +0 +281 +0 + 65 +0 +146 +0.0 + 0 +ENDTAB + 0 +TABLE + 2 +LTYPE + 5 +2 +330 +0 +100 +AcDbSymbolTable + 70 +3 + 0 +LTYPE + 5 +24 +330 +2 +100 +AcDbSymbolTableRecord +100 +AcDbLinetypeTableRecord + 2 +ByBlock + 70 +0 + 3 + + 72 +65 + 73 +0 + 40 +0.0 + 0 +LTYPE + 5 +25 +330 +2 +100 +AcDbSymbolTableRecord +100 +AcDbLinetypeTableRecord + 2 +ByLayer + 70 +0 + 3 + + 72 +65 + 73 +0 + 40 +0.0 + 0 +LTYPE + 5 +26 +330 +2 +100 +AcDbSymbolTableRecord +100 +AcDbLinetypeTableRecord + 2 +Continuous + 70 +0 + 3 + + 72 +65 + 73 +0 + 40 +0.0 + 0 +ENDTAB + 0 +TABLE + 2 +LAYER + 5 +1 +330 +0 +100 +AcDbSymbolTable + 70 +2 + 0 +LAYER + 5 +27 +330 +1 +100 +AcDbSymbolTableRecord +100 +AcDbLayerTableRecord + 2 +0 + 70 +0 + 62 +7 + 6 +Continuous +370 +-3 +390 +13 +347 +21 + 0 +LAYER + 5 +28 +330 +1 +100 +AcDbSymbolTableRecord +100 +AcDbLayerTableRecord + 2 +Defpoints + 70 +0 + 62 +7 + 6 +Continuous +290 +0 +370 +-3 +390 +13 +347 +21 + 0 +ENDTAB + 0 +TABLE + 2 +STYLE + 5 +5 +330 +0 +100 +AcDbSymbolTable + 70 +1 + 0 +STYLE + 5 +29 +330 +5 +100 +AcDbSymbolTableRecord +100 +AcDbTextStyleTableRecord + 2 +Standard + 70 +0 + 40 +0.0 + 41 +1.0 + 50 +0.0 + 71 +0 + 42 +2.5 + 3 +txt + 4 + + 0 +ENDTAB + 0 +TABLE + 2 +VIEW + 5 +7 +330 +0 +100 +AcDbSymbolTable + 70 +0 + 0 +ENDTAB + 0 +TABLE + 2 +UCS + 5 +6 +330 +0 +100 +AcDbSymbolTable + 70 +0 + 0 +ENDTAB + 0 +TABLE + 2 +APPID + 5 +3 +330 +0 +100 +AcDbSymbolTable + 70 +3 + 0 +APPID + 5 +2A +330 +3 +100 +AcDbSymbolTableRecord +100 +AcDbRegAppTableRecord + 2 +ACAD + 70 +0 + 0 +APPID + 5 +35 +330 +3 +100 +AcDbSymbolTableRecord +100 +AcDbRegAppTableRecord + 2 +HATCHBACKGROUNDCOLOR + 70 +0 + 0 +APPID + 5 +36 +330 +3 +100 +AcDbSymbolTableRecord +100 +AcDbRegAppTableRecord + 2 +EZDXF + 70 +0 + 0 +ENDTAB + 0 +TABLE + 2 +DIMSTYLE + 5 +4 +330 +0 +100 +AcDbSymbolTable + 70 +1 +100 +AcDbDimStyleTable + 0 +DIMSTYLE +105 +2B +330 +4 +100 +AcDbSymbolTableRecord +100 +AcDbDimStyleTableRecord + 2 +Standard + 70 +0 + 40 +1.0 + 41 +2.5 + 42 +0.625 + 43 +3.75 + 44 +1.25 + 45 +0.0 + 46 +0.0 + 47 +0.0 + 48 +0.0 + 49 +2.5 +140 +2.5 +141 +2.5 +142 +0.0 +143 +0.03937007874 +144 +1.0 +145 +0.0 +146 +1.0 +147 +0.625 +148 +0.0 + 69 +0 + 70 +0 + 71 +0 + 72 +0 + 73 +0 + 74 +0 + 75 +0 + 76 +0 + 77 +1 + 78 +8 + 79 +3 +170 +0 +171 +3 +172 +1 +173 +0 +174 +0 +175 +0 +176 +0 +177 +0 +178 +0 +179 +2 +271 +2 +272 +2 +273 +2 +274 +3 +275 +0 +276 +0 +277 +2 +278 +44 +279 +0 +280 +0 +281 +0 +282 +0 +283 +0 +284 +8 +285 +0 +286 +0 +288 +0 +289 +3 +290 +0 +371 +-2 +372 +-2 + 0 +ENDTAB + 0 +TABLE + 2 +BLOCK_RECORD + 5 +9 +330 +0 +100 +AcDbSymbolTable + 70 +2 + 0 +BLOCK_RECORD + 5 +17 +330 +9 +100 +AcDbSymbolTableRecord +100 +AcDbBlockTableRecord + 2 +*Model_Space +340 +1A + 70 +0 +280 +1 +281 +0 + 0 +BLOCK_RECORD + 5 +1B +330 +9 +100 +AcDbSymbolTableRecord +100 +AcDbBlockTableRecord + 2 +*Paper_Space +340 +1E + 70 +0 +280 +1 +281 +0 + 0 +ENDTAB + 0 +ENDSEC + 0 +SECTION + 2 +BLOCKS + 0 +BLOCK + 5 +18 +330 +17 +100 +AcDbEntity + 8 +0 +100 +AcDbBlockBegin + 2 +*Model_Space + 70 +0 + 10 +0.0 + 20 +0.0 + 30 +0.0 + 3 +*Model_Space + 1 + + 0 +ENDBLK + 5 +19 +330 +17 +100 +AcDbEntity + 8 +0 +100 +AcDbBlockEnd + 0 +BLOCK + 5 +1C +330 +1B +100 +AcDbEntity + 8 +0 +100 +AcDbBlockBegin + 2 +*Paper_Space + 70 +0 + 10 +0.0 + 20 +0.0 + 30 +0.0 + 3 +*Paper_Space + 1 + + 0 +ENDBLK + 5 +1D +330 +1B +100 +AcDbEntity + 8 +0 +100 +AcDbBlockEnd + 0 +ENDSEC + 0 +SECTION + 2 +ENTITIES + 0 +LWPOLYLINE + 5 +2F +330 +17 +100 +AcDbEntity + 8 +material_rotor_bar +100 +AcDbPolyline + 90 +5 + 70 +0 + 10 +0.0081 + 20 +-0.0008 + 10 +0.0226 + 20 +-0.0024 + 10 +0.0226 + 20 +0.0024 + 10 +0.0081 + 20 +0.0008 + 10 +0.0081 + 20 +-0.0008 + 0 +LWPOLYLINE + 5 +30 +330 +17 +100 +AcDbEntity + 8 +material_rotor_bar +100 +AcDbPolyline + 90 +5 + 70 +0 + 10 +0.0048 + 20 +0.0066 + 10 +0.0134 + 20 +0.0184 + 10 +0.0092 + 20 +0.0208 + 10 +0.0033 + 20 +0.0074 + 10 +0.0048 + 20 +0.0066 + 0 +LWPOLYLINE + 5 +31 +330 +17 +100 +AcDbEntity + 8 +material_rotor_bar +100 +AcDbPolyline + 90 +5 + 70 +0 + 10 +-0.0033 + 20 +0.0074 + 10 +-0.0092 + 20 +0.0208 + 10 +-0.0134 + 20 +0.0184 + 10 +-0.0048 + 20 +0.0066 + 10 +-0.0033 + 20 +0.0074 + 0 +LWPOLYLINE + 5 +32 +330 +17 +100 +AcDbEntity + 8 +material_rotor_bar +100 +AcDbPolyline + 90 +5 + 70 +0 + 10 +-0.0081 + 20 +0.0008 + 10 +-0.0226 + 20 +0.0024 + 10 +-0.0226 + 20 +-0.0024 + 10 +-0.0081 + 20 +-0.0008 + 10 +-0.0081 + 20 +0.0008 + 0 +LWPOLYLINE + 5 +33 +330 +17 +100 +AcDbEntity + 8 +material_rotor_bar +100 +AcDbPolyline + 90 +5 + 70 +0 + 10 +-0.0048 + 20 +-0.0066 + 10 +-0.0134 + 20 +-0.0184 + 10 +-0.0092 + 20 +-0.0208 + 10 +-0.0033 + 20 +-0.0074 + 10 +-0.0048 + 20 +-0.0066 + 0 +LWPOLYLINE + 5 +34 +330 +17 +100 +AcDbEntity + 8 +material_rotor_bar +100 +AcDbPolyline + 90 +5 + 70 +0 + 10 +0.0033 + 20 +-0.0074 + 10 +0.0092 + 20 +-0.0208 + 10 +0.0134 + 20 +-0.0184 + 10 +0.0048 + 20 +-0.0066 + 10 +0.0033 + 20 +-0.0074 + 0 +ENDSEC + 0 +SECTION + 2 +OBJECTS + 0 +DICTIONARY + 5 +A +330 +0 +100 +AcDbDictionary +281 +1 + 3 +ACAD_COLOR +350 +B + 3 +ACAD_GROUP +350 +C + 3 +ACAD_LAYOUT +350 +D + 3 +ACAD_MATERIAL +350 +E + 3 +ACAD_MLEADERSTYLE +350 +F + 3 +ACAD_MLINESTYLE +350 +10 + 3 +ACAD_PLOTSETTINGS +350 +11 + 3 +ACAD_PLOTSTYLENAME +350 +12 + 3 +ACAD_SCALELIST +350 +14 + 3 +ACAD_TABLESTYLE +350 +15 + 3 +ACAD_VISUALSTYLE +350 +16 + 3 +EZDXF_META +350 +2D + 0 +DICTIONARY + 5 +B +330 +A +100 +AcDbDictionary +281 +1 + 0 +DICTIONARY + 5 +C +330 +A +100 +AcDbDictionary +281 +1 + 0 +DICTIONARY + 5 +D +330 +A +100 +AcDbDictionary +281 +1 + 3 +Model +350 +1A + 3 +Layout1 +350 +1E + 0 +DICTIONARY + 5 +E +330 +A +100 +AcDbDictionary +281 +1 + 3 +ByBlock +350 +1F + 3 +ByLayer +350 +20 + 3 +Global +350 +21 + 0 +DICTIONARY + 5 +F +330 +A +100 +AcDbDictionary +281 +1 + 3 +Standard +350 +2C + 0 +DICTIONARY + 5 +10 +330 +A +100 +AcDbDictionary +281 +1 + 3 +Standard +350 +22 + 0 +DICTIONARY + 5 +11 +330 +A +100 +AcDbDictionary +281 +1 + 0 +ACDBDICTIONARYWDFLT + 5 +12 +330 +A +100 +AcDbDictionary +281 +1 + 3 +Normal +350 +13 +100 +AcDbDictionaryWithDefault +340 +13 + 0 +ACDBPLACEHOLDER + 5 +13 +330 +12 + 0 +DICTIONARY + 5 +14 +330 +A +100 +AcDbDictionary +281 +1 + 0 +DICTIONARY + 5 +15 +330 +A +100 +AcDbDictionary +281 +1 + 0 +DICTIONARY + 5 +16 +330 +A +100 +AcDbDictionary +281 +1 + 0 +LAYOUT + 5 +1A +330 +D +100 +AcDbPlotSettings + 1 + + 4 +A3 + 6 + + 40 +7.5 + 41 +20.0 + 42 +7.5 + 43 +20.0 + 44 +420.0 + 45 +297.0 + 46 +0.0 + 47 +0.0 + 48 +0.0 + 49 +0.0 +140 +0.0 +141 +0.0 +142 +1.0 +143 +1.0 + 70 +1024 + 72 +1 + 73 +0 + 74 +5 + 7 + + 75 +16 + 76 +0 + 77 +2 + 78 +300 +147 +1.0 +148 +0.0 +149 +0.0 +100 +AcDbLayout + 1 +Model + 70 +1 + 71 +0 + 10 +0.0 + 20 +0.0 + 11 +420.0 + 21 +297.0 + 12 +0.0 + 22 +0.0 + 32 +0.0 + 14 +1e+20 + 24 +1e+20 + 34 +1e+20 + 15 +-1e+20 + 25 +-1e+20 + 35 +-1e+20 +146 +0.0 + 13 +0.0 + 23 +0.0 + 33 +0.0 + 16 +1.0 + 26 +0.0 + 36 +0.0 + 17 +0.0 + 27 +1.0 + 37 +0.0 + 76 +1 +330 +17 + 0 +LAYOUT + 5 +1E +330 +D +100 +AcDbPlotSettings + 1 + + 4 +A3 + 6 + + 40 +7.5 + 41 +20.0 + 42 +7.5 + 43 +20.0 + 44 +420.0 + 45 +297.0 + 46 +0.0 + 47 +0.0 + 48 +0.0 + 49 +0.0 +140 +0.0 +141 +0.0 +142 +1.0 +143 +1.0 + 70 +0 + 72 +1 + 73 +0 + 74 +5 + 7 + + 75 +16 + 76 +0 + 77 +2 + 78 +300 +147 +1.0 +148 +0.0 +149 +0.0 +100 +AcDbLayout + 1 +Layout1 + 70 +1 + 71 +1 + 10 +0.0 + 20 +0.0 + 11 +420.0 + 21 +297.0 + 12 +0.0 + 22 +0.0 + 32 +0.0 + 14 +1e+20 + 24 +1e+20 + 34 +1e+20 + 15 +-1e+20 + 25 +-1e+20 + 35 +-1e+20 +146 +0.0 + 13 +0.0 + 23 +0.0 + 33 +0.0 + 16 +1.0 + 26 +0.0 + 36 +0.0 + 17 +0.0 + 27 +1.0 + 37 +0.0 + 76 +1 +330 +1B + 0 +MATERIAL + 5 +1F +102 +{ACAD_REACTORS +330 +E +102 +} +330 +E +100 +AcDbMaterial + 1 +ByBlock + 2 + + 70 +0 + 40 +1.0 + 71 +1 + 41 +1.0 + 91 +-1023410177 + 42 +1.0 + 72 +1 + 3 + + 73 +1 + 74 +1 + 75 +1 + 44 +0.5 + 73 +0 + 45 +1.0 + 46 +1.0 + 77 +1 + 4 + + 78 +1 + 79 +1 +170 +1 + 48 +1.0 +171 +1 + 6 + +172 +1 +173 +1 +174 +1 +140 +1.0 +141 +1.0 +175 +1 + 7 + +176 +1 +177 +1 +178 +1 +143 +1.0 +179 +1 + 8 + +270 +1 +271 +1 +272 +1 +145 +1.0 +146 +1.0 +273 +1 + 9 + +274 +1 +275 +1 +276 +1 + 42 +1.0 + 72 +1 + 3 + + 73 +1 + 74 +1 + 75 +1 + 94 +63 + 0 +MATERIAL + 5 +20 +102 +{ACAD_REACTORS +330 +E +102 +} +330 +E +100 +AcDbMaterial + 1 +ByLayer + 2 + + 70 +0 + 40 +1.0 + 71 +1 + 41 +1.0 + 91 +-1023410177 + 42 +1.0 + 72 +1 + 3 + + 73 +1 + 74 +1 + 75 +1 + 44 +0.5 + 73 +0 + 45 +1.0 + 46 +1.0 + 77 +1 + 4 + + 78 +1 + 79 +1 +170 +1 + 48 +1.0 +171 +1 + 6 + +172 +1 +173 +1 +174 +1 +140 +1.0 +141 +1.0 +175 +1 + 7 + +176 +1 +177 +1 +178 +1 +143 +1.0 +179 +1 + 8 + +270 +1 +271 +1 +272 +1 +145 +1.0 +146 +1.0 +273 +1 + 9 + +274 +1 +275 +1 +276 +1 + 42 +1.0 + 72 +1 + 3 + + 73 +1 + 74 +1 + 75 +1 + 94 +63 + 0 +MATERIAL + 5 +21 +102 +{ACAD_REACTORS +330 +E +102 +} +330 +E +100 +AcDbMaterial + 1 +Global + 2 + + 70 +0 + 40 +1.0 + 71 +1 + 41 +1.0 + 91 +-1023410177 + 42 +1.0 + 72 +1 + 3 + + 73 +1 + 74 +1 + 75 +1 + 44 +0.5 + 73 +0 + 45 +1.0 + 46 +1.0 + 77 +1 + 4 + + 78 +1 + 79 +1 +170 +1 + 48 +1.0 +171 +1 + 6 + +172 +1 +173 +1 +174 +1 +140 +1.0 +141 +1.0 +175 +1 + 7 + +176 +1 +177 +1 +178 +1 +143 +1.0 +179 +1 + 8 + +270 +1 +271 +1 +272 +1 +145 +1.0 +146 +1.0 +273 +1 + 9 + +274 +1 +275 +1 +276 +1 + 42 +1.0 + 72 +1 + 3 + + 73 +1 + 74 +1 + 75 +1 + 94 +63 + 0 +MLINESTYLE + 5 +22 +102 +{ACAD_REACTORS +330 +10 +102 +} +330 +10 +100 +AcDbMlineStyle + 2 +Standard + 70 +0 + 3 + + 62 +256 + 51 +90.0 + 52 +90.0 + 71 +2 + 49 +0.5 + 62 +256 + 6 +BYLAYER + 49 +-0.5 + 62 +256 + 6 +BYLAYER + 0 +MLEADERSTYLE + 5 +2C +102 +{ACAD_REACTORS +330 +F +102 +} +330 +F +100 +AcDbMLeaderStyle +179 +2 +170 +2 +171 +1 +172 +0 + 90 +2 + 40 +0.0 + 41 +0.0 +173 +1 + 91 +-1056964608 + 92 +-2 +290 +1 + 42 +2.0 +291 +1 + 43 +8.0 + 3 +Standard + 44 +4.0 +300 + +342 +29 +174 +1 +175 +1 +176 +0 +178 +1 + 93 +-1056964608 + 45 +4.0 +292 +0 +297 +0 + 46 +4.0 + 94 +-1056964608 + 47 +1.0 + 49 +1.0 +140 +1.0 +294 +1 +141 +0.0 +177 +0 +142 +1.0 +295 +0 +296 +0 +143 +3.75 +271 +0 +272 +9 +273 +9 + 0 +DICTIONARY + 5 +2D +330 +A +100 +AcDbDictionary +280 +1 +281 +1 + 3 +CREATED_BY_EZDXF +350 +2E + 3 +WRITTEN_BY_EZDXF +350 +37 + 0 +DICTIONARYVAR + 5 +2E +330 +2D +100 +DictionaryVariables +280 +0 + 1 +1.4.3 @ 2025-11-09T21:04:00.686081+00:00 + 0 +DICTIONARYVAR + 5 +37 +330 +2D +100 +DictionaryVariables +280 +0 + 1 +1.4.3 @ 2025-11-09T21:04:00.688992+00:00 + 0 +ENDSEC + 0 +EOF diff --git a/docs/assets/dxf/induction_demo/rotor_core.dxf b/docs/assets/dxf/induction_demo/rotor_core.dxf new file mode 100644 index 0000000..5331008 --- /dev/null +++ b/docs/assets/dxf/induction_demo/rotor_core.dxf @@ -0,0 +1,3120 @@ + 0 +SECTION + 2 +HEADER + 9 +$ACADVER + 1 +AC1024 + 9 +$ACADMAINTVER + 70 +6 + 9 +$DWGCODEPAGE + 3 +ANSI_1252 + 9 +$LASTSAVEDBY + 1 +ezdxf + 9 +$INSBASE + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$EXTMIN + 10 +1e+20 + 20 +1e+20 + 30 +1e+20 + 9 +$EXTMAX + 10 +-1e+20 + 20 +-1e+20 + 30 +-1e+20 + 9 +$LIMMIN + 10 +0.0 + 20 +0.0 + 9 +$LIMMAX + 10 +420.0 + 20 +297.0 + 9 +$ORTHOMODE + 70 +0 + 9 +$REGENMODE + 70 +1 + 9 +$FILLMODE + 70 +1 + 9 +$QTEXTMODE + 70 +0 + 9 +$MIRRTEXT + 70 +1 + 9 +$LTSCALE + 40 +1.0 + 9 +$ATTMODE + 70 +1 + 9 +$TEXTSIZE + 40 +2.5 + 9 +$TRACEWID + 40 +1.0 + 9 +$TEXTSTYLE + 7 +Standard + 9 +$CLAYER + 8 +0 + 9 +$CELTYPE + 6 +ByLayer + 9 +$CECOLOR + 62 +256 + 9 +$CELTSCALE + 40 +1.0 + 9 +$DISPSILH + 70 +0 + 9 +$DIMSCALE + 40 +1.0 + 9 +$DIMASZ + 40 +2.5 + 9 +$DIMEXO + 40 +0.625 + 9 +$DIMDLI + 40 +3.75 + 9 +$DIMRND + 40 +0.0 + 9 +$DIMDLE + 40 +0.0 + 9 +$DIMEXE + 40 +1.25 + 9 +$DIMTP + 40 +0.0 + 9 +$DIMTM + 40 +0.0 + 9 +$DIMTXT + 40 +2.5 + 9 +$DIMCEN + 40 +2.5 + 9 +$DIMTSZ + 40 +0.0 + 9 +$DIMTOL + 70 +0 + 9 +$DIMLIM + 70 +0 + 9 +$DIMTIH + 70 +0 + 9 +$DIMTOH + 70 +0 + 9 +$DIMSE1 + 70 +0 + 9 +$DIMSE2 + 70 +0 + 9 +$DIMTAD + 70 +1 + 9 +$DIMZIN + 70 +8 + 9 +$DIMBLK + 1 + + 9 +$DIMASO + 70 +1 + 9 +$DIMSHO + 70 +1 + 9 +$DIMPOST + 1 + + 9 +$DIMAPOST + 1 + + 9 +$DIMALT + 70 +0 + 9 +$DIMALTD + 70 +3 + 9 +$DIMALTF + 40 +0.03937007874 + 9 +$DIMLFAC + 40 +1.0 + 9 +$DIMTOFL + 70 +1 + 9 +$DIMTVP + 40 +0.0 + 9 +$DIMTIX + 70 +0 + 9 +$DIMSOXD + 70 +0 + 9 +$DIMSAH + 70 +0 + 9 +$DIMBLK1 + 1 + + 9 +$DIMBLK2 + 1 + + 9 +$DIMSTYLE + 2 +ISO-25 + 9 +$DIMCLRD + 70 +0 + 9 +$DIMCLRE + 70 +0 + 9 +$DIMCLRT + 70 +0 + 9 +$DIMTFAC + 40 +1.0 + 9 +$DIMGAP + 40 +0.625 + 9 +$DIMJUST + 70 +0 + 9 +$DIMSD1 + 70 +0 + 9 +$DIMSD2 + 70 +0 + 9 +$DIMTOLJ + 70 +0 + 9 +$DIMTZIN + 70 +8 + 9 +$DIMALTZ + 70 +0 + 9 +$DIMALTTZ + 70 +0 + 9 +$DIMUPT + 70 +0 + 9 +$DIMDEC + 70 +2 + 9 +$DIMTDEC + 70 +2 + 9 +$DIMALTU + 70 +2 + 9 +$DIMALTTD + 70 +3 + 9 +$DIMTXSTY + 7 +Standard + 9 +$DIMAUNIT + 70 +0 + 9 +$DIMADEC + 70 +0 + 9 +$DIMALTRND + 40 +0.0 + 9 +$DIMAZIN + 70 +0 + 9 +$DIMDSEP + 70 +44 + 9 +$DIMATFIT + 70 +3 + 9 +$DIMFRAC + 70 +0 + 9 +$DIMLDRBLK + 1 + + 9 +$DIMLUNIT + 70 +2 + 9 +$DIMLWD + 70 +-2 + 9 +$DIMLWE + 70 +-2 + 9 +$DIMTMOVE + 70 +0 + 9 +$DIMFXL + 40 +1.0 + 9 +$DIMFXLON + 70 +0 + 9 +$DIMJOGANG + 40 +0.785398163397 + 9 +$DIMTFILL + 70 +0 + 9 +$DIMTFILLCLR + 70 +0 + 9 +$DIMARCSYM + 70 +0 + 9 +$DIMLTYPE + 6 + + 9 +$DIMLTEX1 + 6 + + 9 +$DIMLTEX2 + 6 + + 9 +$DIMTXTDIRECTION + 70 +0 + 9 +$LUNITS + 70 +2 + 9 +$LUPREC + 70 +4 + 9 +$SKETCHINC + 40 +1.0 + 9 +$FILLETRAD + 40 +10.0 + 9 +$AUNITS + 70 +0 + 9 +$AUPREC + 70 +2 + 9 +$MENU + 1 +. + 9 +$ELEVATION + 40 +0.0 + 9 +$PELEVATION + 40 +0.0 + 9 +$THICKNESS + 40 +0.0 + 9 +$LIMCHECK + 70 +0 + 9 +$CHAMFERA + 40 +0.0 + 9 +$CHAMFERB + 40 +0.0 + 9 +$CHAMFERC + 40 +0.0 + 9 +$CHAMFERD + 40 +0.0 + 9 +$SKPOLY + 70 +0 + 9 +$TDCREATE + 40 +2460989.8777777776 + 9 +$TDUCREATE + 40 +2458532.153996898 + 9 +$TDUPDATE + 40 +2460989.8777777776 + 9 +$TDUUPDATE + 40 +2458532.1544311 + 9 +$TDINDWG + 40 +0.0 + 9 +$TDUSRTIMER + 40 +0.0 + 9 +$USRTIMER + 70 +1 + 9 +$ANGBASE + 50 +0.0 + 9 +$ANGDIR + 70 +0 + 9 +$PDMODE + 70 +0 + 9 +$PDSIZE + 40 +0.0 + 9 +$PLINEWID + 40 +0.0 + 9 +$SPLFRAME + 70 +0 + 9 +$SPLINETYPE + 70 +6 + 9 +$SPLINESEGS + 70 +8 + 9 +$HANDSEED + 5 +33 + 9 +$SURFTAB1 + 70 +6 + 9 +$SURFTAB2 + 70 +6 + 9 +$SURFTYPE + 70 +6 + 9 +$SURFU + 70 +6 + 9 +$SURFV + 70 +6 + 9 +$UCSBASE + 2 + + 9 +$UCSNAME + 2 + + 9 +$UCSORG + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$UCSXDIR + 10 +1.0 + 20 +0.0 + 30 +0.0 + 9 +$UCSYDIR + 10 +0.0 + 20 +1.0 + 30 +0.0 + 9 +$UCSORTHOREF + 2 + + 9 +$UCSORTHOVIEW + 70 +0 + 9 +$UCSORGTOP + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$UCSORGBOTTOM + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$UCSORGLEFT + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$UCSORGRIGHT + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$UCSORGFRONT + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$UCSORGBACK + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$PUCSBASE + 2 + + 9 +$PUCSNAME + 2 + + 9 +$PUCSORG + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$PUCSXDIR + 10 +1.0 + 20 +0.0 + 30 +0.0 + 9 +$PUCSYDIR + 10 +0.0 + 20 +1.0 + 30 +0.0 + 9 +$PUCSORTHOREF + 2 + + 9 +$PUCSORTHOVIEW + 70 +0 + 9 +$PUCSORGTOP + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$PUCSORGBOTTOM + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$PUCSORGLEFT + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$PUCSORGRIGHT + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$PUCSORGFRONT + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$PUCSORGBACK + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$USERI1 + 70 +0 + 9 +$USERI2 + 70 +0 + 9 +$USERI3 + 70 +0 + 9 +$USERI4 + 70 +0 + 9 +$USERI5 + 70 +0 + 9 +$USERR1 + 40 +0.0 + 9 +$USERR2 + 40 +0.0 + 9 +$USERR3 + 40 +0.0 + 9 +$USERR4 + 40 +0.0 + 9 +$USERR5 + 40 +0.0 + 9 +$WORLDVIEW + 70 +1 + 9 +$SHADEDGE + 70 +3 + 9 +$SHADEDIF + 70 +70 + 9 +$TILEMODE + 70 +1 + 9 +$MAXACTVP + 70 +64 + 9 +$PINSBASE + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$PLIMCHECK + 70 +0 + 9 +$PEXTMIN + 10 +1e+20 + 20 +1e+20 + 30 +1e+20 + 9 +$PEXTMAX + 10 +-1e+20 + 20 +-1e+20 + 30 +-1e+20 + 9 +$PLIMMIN + 10 +0.0 + 20 +0.0 + 9 +$PLIMMAX + 10 +420.0 + 20 +297.0 + 9 +$UNITMODE + 70 +0 + 9 +$VISRETAIN + 70 +1 + 9 +$PLINEGEN + 70 +0 + 9 +$PSLTSCALE + 70 +1 + 9 +$TREEDEPTH + 70 +3020 + 9 +$CMLSTYLE + 2 +Standard + 9 +$CMLJUST + 70 +0 + 9 +$CMLSCALE + 40 +20.0 + 9 +$PROXYGRAPHICS + 70 +1 + 9 +$MEASUREMENT + 70 +1 + 9 +$CELWEIGHT +370 +-1 + 9 +$ENDCAPS +280 +0 + 9 +$JOINSTYLE +280 +0 + 9 +$LWDISPLAY +290 +0 + 9 +$INSUNITS + 70 +6 + 9 +$HYPERLINKBASE + 1 + + 9 +$STYLESHEET + 1 + + 9 +$XEDIT +290 +1 + 9 +$CEPSNTYPE +380 +0 + 9 +$PSTYLEMODE +290 +1 + 9 +$FINGERPRINTGUID + 2 +{B058C782-883E-4E7C-9C3C-9170640B6BCD} + 9 +$VERSIONGUID + 2 +{9BD3612E-653F-4661-80EE-D1BC492D37FA} + 9 +$EXTNAMES +290 +1 + 9 +$PSVPSCALE + 40 +0.0 + 9 +$OLESTARTUP +290 +0 + 9 +$SORTENTS +280 +127 + 9 +$INDEXCTL +280 +0 + 9 +$HIDETEXT +280 +1 + 9 +$XCLIPFRAME +280 +1 + 9 +$HALOGAP +280 +0 + 9 +$OBSCOLOR + 70 +257 + 9 +$OBSLTYPE +280 +0 + 9 +$INTERSECTIONDISPLAY +280 +0 + 9 +$INTERSECTIONCOLOR + 70 +257 + 9 +$DIMASSOC +280 +2 + 9 +$PROJECTNAME + 1 + + 9 +$CAMERADISPLAY +290 +0 + 9 +$LENSLENGTH + 40 +50.0 + 9 +$CAMERAHEIGHT + 40 +0.0 + 9 +$STEPSPERSEC + 40 +24.0 + 9 +$STEPSIZE + 40 +100.0 + 9 +$3DDWFPREC + 40 +2.0 + 9 +$PSOLWIDTH + 40 +0.005 + 9 +$PSOLHEIGHT + 40 +0.08 + 9 +$LOFTANG1 + 40 +1.570796326795 + 9 +$LOFTANG2 + 40 +1.570796326795 + 9 +$LOFTMAG1 + 40 +0.0 + 9 +$LOFTMAG2 + 40 +0.0 + 9 +$LOFTPARAM + 70 +7 + 9 +$LOFTNORMALS +280 +1 + 9 +$LATITUDE + 40 +37.795 + 9 +$LONGITUDE + 40 +-122.394 + 9 +$NORTHDIRECTION + 40 +0.0 + 9 +$TIMEZONE + 70 +-8000 + 9 +$LIGHTGLYPHDISPLAY +280 +1 + 9 +$TILEMODELIGHTSYNCH +280 +1 + 9 +$CMATERIAL +347 +20 + 9 +$SOLIDHIST +280 +0 + 9 +$SHOWHIST +280 +1 + 9 +$DWFFRAME +280 +2 + 9 +$DGNFRAME +280 +2 + 9 +$REALWORLDSCALE +290 +1 + 9 +$INTERFERECOLOR + 62 +256 + 9 +$CSHADOW +280 +0 + 9 +$SHADOWPLANELOCATION + 40 +0.0 + 0 +ENDSEC + 0 +SECTION + 2 +CLASSES + 0 +CLASS + 1 +ACDBDICTIONARYWDFLT + 2 +AcDbDictionaryWithDefault + 3 +ObjectDBX Classes + 90 +0 + 91 +0 +280 +0 +281 +0 + 0 +CLASS + 1 +SUN + 2 +AcDbSun + 3 +SCENEOE + 90 +1153 + 91 +0 +280 +0 +281 +0 + 0 +CLASS + 1 +VISUALSTYLE + 2 +AcDbVisualStyle + 3 +ObjectDBX Classes + 90 +4095 + 91 +0 +280 +0 +281 +0 + 0 +CLASS + 1 +MATERIAL + 2 +AcDbMaterial + 3 +ObjectDBX Classes + 90 +1153 + 91 +0 +280 +0 +281 +0 + 0 +CLASS + 1 +SCALE + 2 +AcDbScale + 3 +ObjectDBX Classes + 90 +1153 + 91 +0 +280 +0 +281 +0 + 0 +CLASS + 1 +TABLESTYLE + 2 +AcDbTableStyle + 3 +ObjectDBX Classes + 90 +4095 + 91 +0 +280 +0 +281 +0 + 0 +CLASS + 1 +MLEADERSTYLE + 2 +AcDbMLeaderStyle + 3 +ACDB_MLEADERSTYLE_CLASS + 90 +4095 + 91 +0 +280 +0 +281 +0 + 0 +CLASS + 1 +DICTIONARYVAR + 2 +AcDbDictionaryVar + 3 +ObjectDBX Classes + 90 +0 + 91 +0 +280 +0 +281 +0 + 0 +CLASS + 1 +CELLSTYLEMAP + 2 +AcDbCellStyleMap + 3 +ObjectDBX Classes + 90 +1152 + 91 +0 +280 +0 +281 +0 + 0 +CLASS + 1 +MENTALRAYRENDERSETTINGS + 2 +AcDbMentalRayRenderSettings + 3 +SCENEOE + 90 +1024 + 91 +0 +280 +0 +281 +0 + 0 +CLASS + 1 +ACDBDETAILVIEWSTYLE + 2 +AcDbDetailViewStyle + 3 +ObjectDBX Classes + 90 +1025 + 91 +0 +280 +0 +281 +0 + 0 +CLASS + 1 +ACDBSECTIONVIEWSTYLE + 2 +AcDbSectionViewStyle + 3 +ObjectDBX Classes + 90 +1025 + 91 +0 +280 +0 +281 +0 + 0 +CLASS + 1 +RASTERVARIABLES + 2 +AcDbRasterVariables + 3 +ISM + 90 +0 + 91 +0 +280 +0 +281 +0 + 0 +CLASS + 1 +LAYOUT + 2 +AcDbLayout + 3 +ObjectDBX Classes + 90 +0 + 91 +0 +280 +0 +281 +0 + 0 +CLASS + 1 +ACDBPLACEHOLDER + 2 +AcDbPlaceHolder + 3 +ObjectDBX Classes + 90 +0 + 91 +0 +280 +0 +281 +0 + 0 +ENDSEC + 0 +SECTION + 2 +TABLES + 0 +TABLE + 2 +VPORT + 5 +8 +330 +0 +100 +AcDbSymbolTable + 70 +1 + 0 +VPORT + 5 +23 +330 +8 +100 +AcDbSymbolTableRecord +100 +AcDbViewportTableRecord + 2 +*Active + 70 +0 + 10 +0.0 + 20 +0.0 + 11 +1.0 + 21 +1.0 + 12 +0.0 + 22 +0.0 + 13 +0.0 + 23 +0.0 + 14 +0.5 + 24 +0.5 + 15 +0.5 + 25 +0.5 + 16 +0.0 + 26 +0.0 + 36 +1.0 + 17 +0.0 + 27 +0.0 + 37 +0.0 + 40 +1000.0 + 41 +1.34 + 42 +50.0 + 43 +0.0 + 44 +0.0 + 50 +0.0 + 51 +0.0 + 71 +0 + 72 +1000 + 73 +1 + 74 +3 + 75 +0 + 76 +0 + 77 +0 + 78 +0 +281 +0 + 65 +0 +146 +0.0 + 0 +ENDTAB + 0 +TABLE + 2 +LTYPE + 5 +2 +330 +0 +100 +AcDbSymbolTable + 70 +3 + 0 +LTYPE + 5 +24 +330 +2 +100 +AcDbSymbolTableRecord +100 +AcDbLinetypeTableRecord + 2 +ByBlock + 70 +0 + 3 + + 72 +65 + 73 +0 + 40 +0.0 + 0 +LTYPE + 5 +25 +330 +2 +100 +AcDbSymbolTableRecord +100 +AcDbLinetypeTableRecord + 2 +ByLayer + 70 +0 + 3 + + 72 +65 + 73 +0 + 40 +0.0 + 0 +LTYPE + 5 +26 +330 +2 +100 +AcDbSymbolTableRecord +100 +AcDbLinetypeTableRecord + 2 +Continuous + 70 +0 + 3 + + 72 +65 + 73 +0 + 40 +0.0 + 0 +ENDTAB + 0 +TABLE + 2 +LAYER + 5 +1 +330 +0 +100 +AcDbSymbolTable + 70 +2 + 0 +LAYER + 5 +27 +330 +1 +100 +AcDbSymbolTableRecord +100 +AcDbLayerTableRecord + 2 +0 + 70 +0 + 62 +7 + 6 +Continuous +370 +-3 +390 +13 +347 +21 + 0 +LAYER + 5 +28 +330 +1 +100 +AcDbSymbolTableRecord +100 +AcDbLayerTableRecord + 2 +Defpoints + 70 +0 + 62 +7 + 6 +Continuous +290 +0 +370 +-3 +390 +13 +347 +21 + 0 +ENDTAB + 0 +TABLE + 2 +STYLE + 5 +5 +330 +0 +100 +AcDbSymbolTable + 70 +1 + 0 +STYLE + 5 +29 +330 +5 +100 +AcDbSymbolTableRecord +100 +AcDbTextStyleTableRecord + 2 +Standard + 70 +0 + 40 +0.0 + 41 +1.0 + 50 +0.0 + 71 +0 + 42 +2.5 + 3 +txt + 4 + + 0 +ENDTAB + 0 +TABLE + 2 +VIEW + 5 +7 +330 +0 +100 +AcDbSymbolTable + 70 +0 + 0 +ENDTAB + 0 +TABLE + 2 +UCS + 5 +6 +330 +0 +100 +AcDbSymbolTable + 70 +0 + 0 +ENDTAB + 0 +TABLE + 2 +APPID + 5 +3 +330 +0 +100 +AcDbSymbolTable + 70 +3 + 0 +APPID + 5 +2A +330 +3 +100 +AcDbSymbolTableRecord +100 +AcDbRegAppTableRecord + 2 +ACAD + 70 +0 + 0 +APPID + 5 +30 +330 +3 +100 +AcDbSymbolTableRecord +100 +AcDbRegAppTableRecord + 2 +HATCHBACKGROUNDCOLOR + 70 +0 + 0 +APPID + 5 +31 +330 +3 +100 +AcDbSymbolTableRecord +100 +AcDbRegAppTableRecord + 2 +EZDXF + 70 +0 + 0 +ENDTAB + 0 +TABLE + 2 +DIMSTYLE + 5 +4 +330 +0 +100 +AcDbSymbolTable + 70 +1 +100 +AcDbDimStyleTable + 0 +DIMSTYLE +105 +2B +330 +4 +100 +AcDbSymbolTableRecord +100 +AcDbDimStyleTableRecord + 2 +Standard + 70 +0 + 40 +1.0 + 41 +2.5 + 42 +0.625 + 43 +3.75 + 44 +1.25 + 45 +0.0 + 46 +0.0 + 47 +0.0 + 48 +0.0 + 49 +2.5 +140 +2.5 +141 +2.5 +142 +0.0 +143 +0.03937007874 +144 +1.0 +145 +0.0 +146 +1.0 +147 +0.625 +148 +0.0 + 69 +0 + 70 +0 + 71 +0 + 72 +0 + 73 +0 + 74 +0 + 75 +0 + 76 +0 + 77 +1 + 78 +8 + 79 +3 +170 +0 +171 +3 +172 +1 +173 +0 +174 +0 +175 +0 +176 +0 +177 +0 +178 +0 +179 +2 +271 +2 +272 +2 +273 +2 +274 +3 +275 +0 +276 +0 +277 +2 +278 +44 +279 +0 +280 +0 +281 +0 +282 +0 +283 +0 +284 +8 +285 +0 +286 +0 +288 +0 +289 +3 +290 +0 +371 +-2 +372 +-2 + 0 +ENDTAB + 0 +TABLE + 2 +BLOCK_RECORD + 5 +9 +330 +0 +100 +AcDbSymbolTable + 70 +2 + 0 +BLOCK_RECORD + 5 +17 +330 +9 +100 +AcDbSymbolTableRecord +100 +AcDbBlockTableRecord + 2 +*Model_Space +340 +1A + 70 +0 +280 +1 +281 +0 + 0 +BLOCK_RECORD + 5 +1B +330 +9 +100 +AcDbSymbolTableRecord +100 +AcDbBlockTableRecord + 2 +*Paper_Space +340 +1E + 70 +0 +280 +1 +281 +0 + 0 +ENDTAB + 0 +ENDSEC + 0 +SECTION + 2 +BLOCKS + 0 +BLOCK + 5 +18 +330 +17 +100 +AcDbEntity + 8 +0 +100 +AcDbBlockBegin + 2 +*Model_Space + 70 +0 + 10 +0.0 + 20 +0.0 + 30 +0.0 + 3 +*Model_Space + 1 + + 0 +ENDBLK + 5 +19 +330 +17 +100 +AcDbEntity + 8 +0 +100 +AcDbBlockEnd + 0 +BLOCK + 5 +1C +330 +1B +100 +AcDbEntity + 8 +0 +100 +AcDbBlockBegin + 2 +*Paper_Space + 70 +0 + 10 +0.0 + 20 +0.0 + 30 +0.0 + 3 +*Paper_Space + 1 + + 0 +ENDBLK + 5 +1D +330 +1B +100 +AcDbEntity + 8 +0 +100 +AcDbBlockEnd + 0 +ENDSEC + 0 +SECTION + 2 +ENTITIES + 0 +LWPOLYLINE + 5 +2F +330 +17 +100 +AcDbEntity + 8 +material_rotor_core +100 +AcDbPolyline + 90 +15 + 70 +0 + 10 +0.0232 + 20 +0.0 + 10 +0.0209 + 20 +0.0101 + 10 +0.0145 + 20 +0.0181 + 10 +0.0052 + 20 +0.0226 + 10 +-0.0052 + 20 +0.0226 + 10 +-0.0145 + 20 +0.0181 + 10 +-0.0209 + 20 +0.0101 + 10 +-0.0232 + 20 +0.0 + 10 +-0.0209 + 20 +-0.0101 + 10 +-0.0145 + 20 +-0.0181 + 10 +-0.0052 + 20 +-0.0226 + 10 +0.0052 + 20 +-0.0226 + 10 +0.0145 + 20 +-0.0181 + 10 +0.0209 + 20 +-0.0101 + 10 +0.0232 + 20 +0.0 + 0 +ENDSEC + 0 +SECTION + 2 +OBJECTS + 0 +DICTIONARY + 5 +A +330 +0 +100 +AcDbDictionary +281 +1 + 3 +ACAD_COLOR +350 +B + 3 +ACAD_GROUP +350 +C + 3 +ACAD_LAYOUT +350 +D + 3 +ACAD_MATERIAL +350 +E + 3 +ACAD_MLEADERSTYLE +350 +F + 3 +ACAD_MLINESTYLE +350 +10 + 3 +ACAD_PLOTSETTINGS +350 +11 + 3 +ACAD_PLOTSTYLENAME +350 +12 + 3 +ACAD_SCALELIST +350 +14 + 3 +ACAD_TABLESTYLE +350 +15 + 3 +ACAD_VISUALSTYLE +350 +16 + 3 +EZDXF_META +350 +2D + 0 +DICTIONARY + 5 +B +330 +A +100 +AcDbDictionary +281 +1 + 0 +DICTIONARY + 5 +C +330 +A +100 +AcDbDictionary +281 +1 + 0 +DICTIONARY + 5 +D +330 +A +100 +AcDbDictionary +281 +1 + 3 +Model +350 +1A + 3 +Layout1 +350 +1E + 0 +DICTIONARY + 5 +E +330 +A +100 +AcDbDictionary +281 +1 + 3 +ByBlock +350 +1F + 3 +ByLayer +350 +20 + 3 +Global +350 +21 + 0 +DICTIONARY + 5 +F +330 +A +100 +AcDbDictionary +281 +1 + 3 +Standard +350 +2C + 0 +DICTIONARY + 5 +10 +330 +A +100 +AcDbDictionary +281 +1 + 3 +Standard +350 +22 + 0 +DICTIONARY + 5 +11 +330 +A +100 +AcDbDictionary +281 +1 + 0 +ACDBDICTIONARYWDFLT + 5 +12 +330 +A +100 +AcDbDictionary +281 +1 + 3 +Normal +350 +13 +100 +AcDbDictionaryWithDefault +340 +13 + 0 +ACDBPLACEHOLDER + 5 +13 +330 +12 + 0 +DICTIONARY + 5 +14 +330 +A +100 +AcDbDictionary +281 +1 + 0 +DICTIONARY + 5 +15 +330 +A +100 +AcDbDictionary +281 +1 + 0 +DICTIONARY + 5 +16 +330 +A +100 +AcDbDictionary +281 +1 + 0 +LAYOUT + 5 +1A +330 +D +100 +AcDbPlotSettings + 1 + + 4 +A3 + 6 + + 40 +7.5 + 41 +20.0 + 42 +7.5 + 43 +20.0 + 44 +420.0 + 45 +297.0 + 46 +0.0 + 47 +0.0 + 48 +0.0 + 49 +0.0 +140 +0.0 +141 +0.0 +142 +1.0 +143 +1.0 + 70 +1024 + 72 +1 + 73 +0 + 74 +5 + 7 + + 75 +16 + 76 +0 + 77 +2 + 78 +300 +147 +1.0 +148 +0.0 +149 +0.0 +100 +AcDbLayout + 1 +Model + 70 +1 + 71 +0 + 10 +0.0 + 20 +0.0 + 11 +420.0 + 21 +297.0 + 12 +0.0 + 22 +0.0 + 32 +0.0 + 14 +1e+20 + 24 +1e+20 + 34 +1e+20 + 15 +-1e+20 + 25 +-1e+20 + 35 +-1e+20 +146 +0.0 + 13 +0.0 + 23 +0.0 + 33 +0.0 + 16 +1.0 + 26 +0.0 + 36 +0.0 + 17 +0.0 + 27 +1.0 + 37 +0.0 + 76 +1 +330 +17 + 0 +LAYOUT + 5 +1E +330 +D +100 +AcDbPlotSettings + 1 + + 4 +A3 + 6 + + 40 +7.5 + 41 +20.0 + 42 +7.5 + 43 +20.0 + 44 +420.0 + 45 +297.0 + 46 +0.0 + 47 +0.0 + 48 +0.0 + 49 +0.0 +140 +0.0 +141 +0.0 +142 +1.0 +143 +1.0 + 70 +0 + 72 +1 + 73 +0 + 74 +5 + 7 + + 75 +16 + 76 +0 + 77 +2 + 78 +300 +147 +1.0 +148 +0.0 +149 +0.0 +100 +AcDbLayout + 1 +Layout1 + 70 +1 + 71 +1 + 10 +0.0 + 20 +0.0 + 11 +420.0 + 21 +297.0 + 12 +0.0 + 22 +0.0 + 32 +0.0 + 14 +1e+20 + 24 +1e+20 + 34 +1e+20 + 15 +-1e+20 + 25 +-1e+20 + 35 +-1e+20 +146 +0.0 + 13 +0.0 + 23 +0.0 + 33 +0.0 + 16 +1.0 + 26 +0.0 + 36 +0.0 + 17 +0.0 + 27 +1.0 + 37 +0.0 + 76 +1 +330 +1B + 0 +MATERIAL + 5 +1F +102 +{ACAD_REACTORS +330 +E +102 +} +330 +E +100 +AcDbMaterial + 1 +ByBlock + 2 + + 70 +0 + 40 +1.0 + 71 +1 + 41 +1.0 + 91 +-1023410177 + 42 +1.0 + 72 +1 + 3 + + 73 +1 + 74 +1 + 75 +1 + 44 +0.5 + 73 +0 + 45 +1.0 + 46 +1.0 + 77 +1 + 4 + + 78 +1 + 79 +1 +170 +1 + 48 +1.0 +171 +1 + 6 + +172 +1 +173 +1 +174 +1 +140 +1.0 +141 +1.0 +175 +1 + 7 + +176 +1 +177 +1 +178 +1 +143 +1.0 +179 +1 + 8 + +270 +1 +271 +1 +272 +1 +145 +1.0 +146 +1.0 +273 +1 + 9 + +274 +1 +275 +1 +276 +1 + 42 +1.0 + 72 +1 + 3 + + 73 +1 + 74 +1 + 75 +1 + 94 +63 + 0 +MATERIAL + 5 +20 +102 +{ACAD_REACTORS +330 +E +102 +} +330 +E +100 +AcDbMaterial + 1 +ByLayer + 2 + + 70 +0 + 40 +1.0 + 71 +1 + 41 +1.0 + 91 +-1023410177 + 42 +1.0 + 72 +1 + 3 + + 73 +1 + 74 +1 + 75 +1 + 44 +0.5 + 73 +0 + 45 +1.0 + 46 +1.0 + 77 +1 + 4 + + 78 +1 + 79 +1 +170 +1 + 48 +1.0 +171 +1 + 6 + +172 +1 +173 +1 +174 +1 +140 +1.0 +141 +1.0 +175 +1 + 7 + +176 +1 +177 +1 +178 +1 +143 +1.0 +179 +1 + 8 + +270 +1 +271 +1 +272 +1 +145 +1.0 +146 +1.0 +273 +1 + 9 + +274 +1 +275 +1 +276 +1 + 42 +1.0 + 72 +1 + 3 + + 73 +1 + 74 +1 + 75 +1 + 94 +63 + 0 +MATERIAL + 5 +21 +102 +{ACAD_REACTORS +330 +E +102 +} +330 +E +100 +AcDbMaterial + 1 +Global + 2 + + 70 +0 + 40 +1.0 + 71 +1 + 41 +1.0 + 91 +-1023410177 + 42 +1.0 + 72 +1 + 3 + + 73 +1 + 74 +1 + 75 +1 + 44 +0.5 + 73 +0 + 45 +1.0 + 46 +1.0 + 77 +1 + 4 + + 78 +1 + 79 +1 +170 +1 + 48 +1.0 +171 +1 + 6 + +172 +1 +173 +1 +174 +1 +140 +1.0 +141 +1.0 +175 +1 + 7 + +176 +1 +177 +1 +178 +1 +143 +1.0 +179 +1 + 8 + +270 +1 +271 +1 +272 +1 +145 +1.0 +146 +1.0 +273 +1 + 9 + +274 +1 +275 +1 +276 +1 + 42 +1.0 + 72 +1 + 3 + + 73 +1 + 74 +1 + 75 +1 + 94 +63 + 0 +MLINESTYLE + 5 +22 +102 +{ACAD_REACTORS +330 +10 +102 +} +330 +10 +100 +AcDbMlineStyle + 2 +Standard + 70 +0 + 3 + + 62 +256 + 51 +90.0 + 52 +90.0 + 71 +2 + 49 +0.5 + 62 +256 + 6 +BYLAYER + 49 +-0.5 + 62 +256 + 6 +BYLAYER + 0 +MLEADERSTYLE + 5 +2C +102 +{ACAD_REACTORS +330 +F +102 +} +330 +F +100 +AcDbMLeaderStyle +179 +2 +170 +2 +171 +1 +172 +0 + 90 +2 + 40 +0.0 + 41 +0.0 +173 +1 + 91 +-1056964608 + 92 +-2 +290 +1 + 42 +2.0 +291 +1 + 43 +8.0 + 3 +Standard + 44 +4.0 +300 + +342 +29 +174 +1 +175 +1 +176 +0 +178 +1 + 93 +-1056964608 + 45 +4.0 +292 +0 +297 +0 + 46 +4.0 + 94 +-1056964608 + 47 +1.0 + 49 +1.0 +140 +1.0 +294 +1 +141 +0.0 +177 +0 +142 +1.0 +295 +0 +296 +0 +143 +3.75 +271 +0 +272 +9 +273 +9 + 0 +DICTIONARY + 5 +2D +330 +A +100 +AcDbDictionary +280 +1 +281 +1 + 3 +CREATED_BY_EZDXF +350 +2E + 3 +WRITTEN_BY_EZDXF +350 +32 + 0 +DICTIONARYVAR + 5 +2E +330 +2D +100 +DictionaryVariables +280 +0 + 1 +1.4.3 @ 2025-11-09T21:04:00.667315+00:00 + 0 +DICTIONARYVAR + 5 +32 +330 +2D +100 +DictionaryVariables +280 +0 + 1 +1.4.3 @ 2025-11-09T21:04:00.670715+00:00 + 0 +ENDSEC + 0 +EOF diff --git a/docs/assets/dxf/induction_demo/stator_only.dxf b/docs/assets/dxf/induction_demo/stator_only.dxf new file mode 100644 index 0000000..59c42e9 --- /dev/null +++ b/docs/assets/dxf/induction_demo/stator_only.dxf @@ -0,0 +1,3160 @@ + 0 +SECTION + 2 +HEADER + 9 +$ACADVER + 1 +AC1024 + 9 +$ACADMAINTVER + 70 +6 + 9 +$DWGCODEPAGE + 3 +ANSI_1252 + 9 +$LASTSAVEDBY + 1 +ezdxf + 9 +$INSBASE + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$EXTMIN + 10 +1e+20 + 20 +1e+20 + 30 +1e+20 + 9 +$EXTMAX + 10 +-1e+20 + 20 +-1e+20 + 30 +-1e+20 + 9 +$LIMMIN + 10 +0.0 + 20 +0.0 + 9 +$LIMMAX + 10 +420.0 + 20 +297.0 + 9 +$ORTHOMODE + 70 +0 + 9 +$REGENMODE + 70 +1 + 9 +$FILLMODE + 70 +1 + 9 +$QTEXTMODE + 70 +0 + 9 +$MIRRTEXT + 70 +1 + 9 +$LTSCALE + 40 +1.0 + 9 +$ATTMODE + 70 +1 + 9 +$TEXTSIZE + 40 +2.5 + 9 +$TRACEWID + 40 +1.0 + 9 +$TEXTSTYLE + 7 +Standard + 9 +$CLAYER + 8 +0 + 9 +$CELTYPE + 6 +ByLayer + 9 +$CECOLOR + 62 +256 + 9 +$CELTSCALE + 40 +1.0 + 9 +$DISPSILH + 70 +0 + 9 +$DIMSCALE + 40 +1.0 + 9 +$DIMASZ + 40 +2.5 + 9 +$DIMEXO + 40 +0.625 + 9 +$DIMDLI + 40 +3.75 + 9 +$DIMRND + 40 +0.0 + 9 +$DIMDLE + 40 +0.0 + 9 +$DIMEXE + 40 +1.25 + 9 +$DIMTP + 40 +0.0 + 9 +$DIMTM + 40 +0.0 + 9 +$DIMTXT + 40 +2.5 + 9 +$DIMCEN + 40 +2.5 + 9 +$DIMTSZ + 40 +0.0 + 9 +$DIMTOL + 70 +0 + 9 +$DIMLIM + 70 +0 + 9 +$DIMTIH + 70 +0 + 9 +$DIMTOH + 70 +0 + 9 +$DIMSE1 + 70 +0 + 9 +$DIMSE2 + 70 +0 + 9 +$DIMTAD + 70 +1 + 9 +$DIMZIN + 70 +8 + 9 +$DIMBLK + 1 + + 9 +$DIMASO + 70 +1 + 9 +$DIMSHO + 70 +1 + 9 +$DIMPOST + 1 + + 9 +$DIMAPOST + 1 + + 9 +$DIMALT + 70 +0 + 9 +$DIMALTD + 70 +3 + 9 +$DIMALTF + 40 +0.03937007874 + 9 +$DIMLFAC + 40 +1.0 + 9 +$DIMTOFL + 70 +1 + 9 +$DIMTVP + 40 +0.0 + 9 +$DIMTIX + 70 +0 + 9 +$DIMSOXD + 70 +0 + 9 +$DIMSAH + 70 +0 + 9 +$DIMBLK1 + 1 + + 9 +$DIMBLK2 + 1 + + 9 +$DIMSTYLE + 2 +ISO-25 + 9 +$DIMCLRD + 70 +0 + 9 +$DIMCLRE + 70 +0 + 9 +$DIMCLRT + 70 +0 + 9 +$DIMTFAC + 40 +1.0 + 9 +$DIMGAP + 40 +0.625 + 9 +$DIMJUST + 70 +0 + 9 +$DIMSD1 + 70 +0 + 9 +$DIMSD2 + 70 +0 + 9 +$DIMTOLJ + 70 +0 + 9 +$DIMTZIN + 70 +8 + 9 +$DIMALTZ + 70 +0 + 9 +$DIMALTTZ + 70 +0 + 9 +$DIMUPT + 70 +0 + 9 +$DIMDEC + 70 +2 + 9 +$DIMTDEC + 70 +2 + 9 +$DIMALTU + 70 +2 + 9 +$DIMALTTD + 70 +3 + 9 +$DIMTXSTY + 7 +Standard + 9 +$DIMAUNIT + 70 +0 + 9 +$DIMADEC + 70 +0 + 9 +$DIMALTRND + 40 +0.0 + 9 +$DIMAZIN + 70 +0 + 9 +$DIMDSEP + 70 +44 + 9 +$DIMATFIT + 70 +3 + 9 +$DIMFRAC + 70 +0 + 9 +$DIMLDRBLK + 1 + + 9 +$DIMLUNIT + 70 +2 + 9 +$DIMLWD + 70 +-2 + 9 +$DIMLWE + 70 +-2 + 9 +$DIMTMOVE + 70 +0 + 9 +$DIMFXL + 40 +1.0 + 9 +$DIMFXLON + 70 +0 + 9 +$DIMJOGANG + 40 +0.785398163397 + 9 +$DIMTFILL + 70 +0 + 9 +$DIMTFILLCLR + 70 +0 + 9 +$DIMARCSYM + 70 +0 + 9 +$DIMLTYPE + 6 + + 9 +$DIMLTEX1 + 6 + + 9 +$DIMLTEX2 + 6 + + 9 +$DIMTXTDIRECTION + 70 +0 + 9 +$LUNITS + 70 +2 + 9 +$LUPREC + 70 +4 + 9 +$SKETCHINC + 40 +1.0 + 9 +$FILLETRAD + 40 +10.0 + 9 +$AUNITS + 70 +0 + 9 +$AUPREC + 70 +2 + 9 +$MENU + 1 +. + 9 +$ELEVATION + 40 +0.0 + 9 +$PELEVATION + 40 +0.0 + 9 +$THICKNESS + 40 +0.0 + 9 +$LIMCHECK + 70 +0 + 9 +$CHAMFERA + 40 +0.0 + 9 +$CHAMFERB + 40 +0.0 + 9 +$CHAMFERC + 40 +0.0 + 9 +$CHAMFERD + 40 +0.0 + 9 +$SKPOLY + 70 +0 + 9 +$TDCREATE + 40 +2460989.8777777776 + 9 +$TDUCREATE + 40 +2458532.153996898 + 9 +$TDUPDATE + 40 +2460989.8777777776 + 9 +$TDUUPDATE + 40 +2458532.1544311 + 9 +$TDINDWG + 40 +0.0 + 9 +$TDUSRTIMER + 40 +0.0 + 9 +$USRTIMER + 70 +1 + 9 +$ANGBASE + 50 +0.0 + 9 +$ANGDIR + 70 +0 + 9 +$PDMODE + 70 +0 + 9 +$PDSIZE + 40 +0.0 + 9 +$PLINEWID + 40 +0.0 + 9 +$SPLFRAME + 70 +0 + 9 +$SPLINETYPE + 70 +6 + 9 +$SPLINESEGS + 70 +8 + 9 +$HANDSEED + 5 +33 + 9 +$SURFTAB1 + 70 +6 + 9 +$SURFTAB2 + 70 +6 + 9 +$SURFTYPE + 70 +6 + 9 +$SURFU + 70 +6 + 9 +$SURFV + 70 +6 + 9 +$UCSBASE + 2 + + 9 +$UCSNAME + 2 + + 9 +$UCSORG + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$UCSXDIR + 10 +1.0 + 20 +0.0 + 30 +0.0 + 9 +$UCSYDIR + 10 +0.0 + 20 +1.0 + 30 +0.0 + 9 +$UCSORTHOREF + 2 + + 9 +$UCSORTHOVIEW + 70 +0 + 9 +$UCSORGTOP + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$UCSORGBOTTOM + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$UCSORGLEFT + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$UCSORGRIGHT + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$UCSORGFRONT + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$UCSORGBACK + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$PUCSBASE + 2 + + 9 +$PUCSNAME + 2 + + 9 +$PUCSORG + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$PUCSXDIR + 10 +1.0 + 20 +0.0 + 30 +0.0 + 9 +$PUCSYDIR + 10 +0.0 + 20 +1.0 + 30 +0.0 + 9 +$PUCSORTHOREF + 2 + + 9 +$PUCSORTHOVIEW + 70 +0 + 9 +$PUCSORGTOP + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$PUCSORGBOTTOM + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$PUCSORGLEFT + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$PUCSORGRIGHT + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$PUCSORGFRONT + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$PUCSORGBACK + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$USERI1 + 70 +0 + 9 +$USERI2 + 70 +0 + 9 +$USERI3 + 70 +0 + 9 +$USERI4 + 70 +0 + 9 +$USERI5 + 70 +0 + 9 +$USERR1 + 40 +0.0 + 9 +$USERR2 + 40 +0.0 + 9 +$USERR3 + 40 +0.0 + 9 +$USERR4 + 40 +0.0 + 9 +$USERR5 + 40 +0.0 + 9 +$WORLDVIEW + 70 +1 + 9 +$SHADEDGE + 70 +3 + 9 +$SHADEDIF + 70 +70 + 9 +$TILEMODE + 70 +1 + 9 +$MAXACTVP + 70 +64 + 9 +$PINSBASE + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$PLIMCHECK + 70 +0 + 9 +$PEXTMIN + 10 +1e+20 + 20 +1e+20 + 30 +1e+20 + 9 +$PEXTMAX + 10 +-1e+20 + 20 +-1e+20 + 30 +-1e+20 + 9 +$PLIMMIN + 10 +0.0 + 20 +0.0 + 9 +$PLIMMAX + 10 +420.0 + 20 +297.0 + 9 +$UNITMODE + 70 +0 + 9 +$VISRETAIN + 70 +1 + 9 +$PLINEGEN + 70 +0 + 9 +$PSLTSCALE + 70 +1 + 9 +$TREEDEPTH + 70 +3020 + 9 +$CMLSTYLE + 2 +Standard + 9 +$CMLJUST + 70 +0 + 9 +$CMLSCALE + 40 +20.0 + 9 +$PROXYGRAPHICS + 70 +1 + 9 +$MEASUREMENT + 70 +1 + 9 +$CELWEIGHT +370 +-1 + 9 +$ENDCAPS +280 +0 + 9 +$JOINSTYLE +280 +0 + 9 +$LWDISPLAY +290 +0 + 9 +$INSUNITS + 70 +6 + 9 +$HYPERLINKBASE + 1 + + 9 +$STYLESHEET + 1 + + 9 +$XEDIT +290 +1 + 9 +$CEPSNTYPE +380 +0 + 9 +$PSTYLEMODE +290 +1 + 9 +$FINGERPRINTGUID + 2 +{D42408CD-9EED-4F0D-8F20-79DB8556A4A6} + 9 +$VERSIONGUID + 2 +{094D7FB6-E9B8-402F-A092-AD2C044ABDF6} + 9 +$EXTNAMES +290 +1 + 9 +$PSVPSCALE + 40 +0.0 + 9 +$OLESTARTUP +290 +0 + 9 +$SORTENTS +280 +127 + 9 +$INDEXCTL +280 +0 + 9 +$HIDETEXT +280 +1 + 9 +$XCLIPFRAME +280 +1 + 9 +$HALOGAP +280 +0 + 9 +$OBSCOLOR + 70 +257 + 9 +$OBSLTYPE +280 +0 + 9 +$INTERSECTIONDISPLAY +280 +0 + 9 +$INTERSECTIONCOLOR + 70 +257 + 9 +$DIMASSOC +280 +2 + 9 +$PROJECTNAME + 1 + + 9 +$CAMERADISPLAY +290 +0 + 9 +$LENSLENGTH + 40 +50.0 + 9 +$CAMERAHEIGHT + 40 +0.0 + 9 +$STEPSPERSEC + 40 +24.0 + 9 +$STEPSIZE + 40 +100.0 + 9 +$3DDWFPREC + 40 +2.0 + 9 +$PSOLWIDTH + 40 +0.005 + 9 +$PSOLHEIGHT + 40 +0.08 + 9 +$LOFTANG1 + 40 +1.570796326795 + 9 +$LOFTANG2 + 40 +1.570796326795 + 9 +$LOFTMAG1 + 40 +0.0 + 9 +$LOFTMAG2 + 40 +0.0 + 9 +$LOFTPARAM + 70 +7 + 9 +$LOFTNORMALS +280 +1 + 9 +$LATITUDE + 40 +37.795 + 9 +$LONGITUDE + 40 +-122.394 + 9 +$NORTHDIRECTION + 40 +0.0 + 9 +$TIMEZONE + 70 +-8000 + 9 +$LIGHTGLYPHDISPLAY +280 +1 + 9 +$TILEMODELIGHTSYNCH +280 +1 + 9 +$CMATERIAL +347 +20 + 9 +$SOLIDHIST +280 +0 + 9 +$SHOWHIST +280 +1 + 9 +$DWFFRAME +280 +2 + 9 +$DGNFRAME +280 +2 + 9 +$REALWORLDSCALE +290 +1 + 9 +$INTERFERECOLOR + 62 +256 + 9 +$CSHADOW +280 +0 + 9 +$SHADOWPLANELOCATION + 40 +0.0 + 0 +ENDSEC + 0 +SECTION + 2 +CLASSES + 0 +CLASS + 1 +ACDBDICTIONARYWDFLT + 2 +AcDbDictionaryWithDefault + 3 +ObjectDBX Classes + 90 +0 + 91 +0 +280 +0 +281 +0 + 0 +CLASS + 1 +SUN + 2 +AcDbSun + 3 +SCENEOE + 90 +1153 + 91 +0 +280 +0 +281 +0 + 0 +CLASS + 1 +VISUALSTYLE + 2 +AcDbVisualStyle + 3 +ObjectDBX Classes + 90 +4095 + 91 +0 +280 +0 +281 +0 + 0 +CLASS + 1 +MATERIAL + 2 +AcDbMaterial + 3 +ObjectDBX Classes + 90 +1153 + 91 +0 +280 +0 +281 +0 + 0 +CLASS + 1 +SCALE + 2 +AcDbScale + 3 +ObjectDBX Classes + 90 +1153 + 91 +0 +280 +0 +281 +0 + 0 +CLASS + 1 +TABLESTYLE + 2 +AcDbTableStyle + 3 +ObjectDBX Classes + 90 +4095 + 91 +0 +280 +0 +281 +0 + 0 +CLASS + 1 +MLEADERSTYLE + 2 +AcDbMLeaderStyle + 3 +ACDB_MLEADERSTYLE_CLASS + 90 +4095 + 91 +0 +280 +0 +281 +0 + 0 +CLASS + 1 +DICTIONARYVAR + 2 +AcDbDictionaryVar + 3 +ObjectDBX Classes + 90 +0 + 91 +0 +280 +0 +281 +0 + 0 +CLASS + 1 +CELLSTYLEMAP + 2 +AcDbCellStyleMap + 3 +ObjectDBX Classes + 90 +1152 + 91 +0 +280 +0 +281 +0 + 0 +CLASS + 1 +MENTALRAYRENDERSETTINGS + 2 +AcDbMentalRayRenderSettings + 3 +SCENEOE + 90 +1024 + 91 +0 +280 +0 +281 +0 + 0 +CLASS + 1 +ACDBDETAILVIEWSTYLE + 2 +AcDbDetailViewStyle + 3 +ObjectDBX Classes + 90 +1025 + 91 +0 +280 +0 +281 +0 + 0 +CLASS + 1 +ACDBSECTIONVIEWSTYLE + 2 +AcDbSectionViewStyle + 3 +ObjectDBX Classes + 90 +1025 + 91 +0 +280 +0 +281 +0 + 0 +CLASS + 1 +RASTERVARIABLES + 2 +AcDbRasterVariables + 3 +ISM + 90 +0 + 91 +0 +280 +0 +281 +0 + 0 +CLASS + 1 +LAYOUT + 2 +AcDbLayout + 3 +ObjectDBX Classes + 90 +0 + 91 +0 +280 +0 +281 +0 + 0 +CLASS + 1 +ACDBPLACEHOLDER + 2 +AcDbPlaceHolder + 3 +ObjectDBX Classes + 90 +0 + 91 +0 +280 +0 +281 +0 + 0 +ENDSEC + 0 +SECTION + 2 +TABLES + 0 +TABLE + 2 +VPORT + 5 +8 +330 +0 +100 +AcDbSymbolTable + 70 +1 + 0 +VPORT + 5 +23 +330 +8 +100 +AcDbSymbolTableRecord +100 +AcDbViewportTableRecord + 2 +*Active + 70 +0 + 10 +0.0 + 20 +0.0 + 11 +1.0 + 21 +1.0 + 12 +0.0 + 22 +0.0 + 13 +0.0 + 23 +0.0 + 14 +0.5 + 24 +0.5 + 15 +0.5 + 25 +0.5 + 16 +0.0 + 26 +0.0 + 36 +1.0 + 17 +0.0 + 27 +0.0 + 37 +0.0 + 40 +1000.0 + 41 +1.34 + 42 +50.0 + 43 +0.0 + 44 +0.0 + 50 +0.0 + 51 +0.0 + 71 +0 + 72 +1000 + 73 +1 + 74 +3 + 75 +0 + 76 +0 + 77 +0 + 78 +0 +281 +0 + 65 +0 +146 +0.0 + 0 +ENDTAB + 0 +TABLE + 2 +LTYPE + 5 +2 +330 +0 +100 +AcDbSymbolTable + 70 +3 + 0 +LTYPE + 5 +24 +330 +2 +100 +AcDbSymbolTableRecord +100 +AcDbLinetypeTableRecord + 2 +ByBlock + 70 +0 + 3 + + 72 +65 + 73 +0 + 40 +0.0 + 0 +LTYPE + 5 +25 +330 +2 +100 +AcDbSymbolTableRecord +100 +AcDbLinetypeTableRecord + 2 +ByLayer + 70 +0 + 3 + + 72 +65 + 73 +0 + 40 +0.0 + 0 +LTYPE + 5 +26 +330 +2 +100 +AcDbSymbolTableRecord +100 +AcDbLinetypeTableRecord + 2 +Continuous + 70 +0 + 3 + + 72 +65 + 73 +0 + 40 +0.0 + 0 +ENDTAB + 0 +TABLE + 2 +LAYER + 5 +1 +330 +0 +100 +AcDbSymbolTable + 70 +2 + 0 +LAYER + 5 +27 +330 +1 +100 +AcDbSymbolTableRecord +100 +AcDbLayerTableRecord + 2 +0 + 70 +0 + 62 +7 + 6 +Continuous +370 +-3 +390 +13 +347 +21 + 0 +LAYER + 5 +28 +330 +1 +100 +AcDbSymbolTableRecord +100 +AcDbLayerTableRecord + 2 +Defpoints + 70 +0 + 62 +7 + 6 +Continuous +290 +0 +370 +-3 +390 +13 +347 +21 + 0 +ENDTAB + 0 +TABLE + 2 +STYLE + 5 +5 +330 +0 +100 +AcDbSymbolTable + 70 +1 + 0 +STYLE + 5 +29 +330 +5 +100 +AcDbSymbolTableRecord +100 +AcDbTextStyleTableRecord + 2 +Standard + 70 +0 + 40 +0.0 + 41 +1.0 + 50 +0.0 + 71 +0 + 42 +2.5 + 3 +txt + 4 + + 0 +ENDTAB + 0 +TABLE + 2 +VIEW + 5 +7 +330 +0 +100 +AcDbSymbolTable + 70 +0 + 0 +ENDTAB + 0 +TABLE + 2 +UCS + 5 +6 +330 +0 +100 +AcDbSymbolTable + 70 +0 + 0 +ENDTAB + 0 +TABLE + 2 +APPID + 5 +3 +330 +0 +100 +AcDbSymbolTable + 70 +3 + 0 +APPID + 5 +2A +330 +3 +100 +AcDbSymbolTableRecord +100 +AcDbRegAppTableRecord + 2 +ACAD + 70 +0 + 0 +APPID + 5 +30 +330 +3 +100 +AcDbSymbolTableRecord +100 +AcDbRegAppTableRecord + 2 +HATCHBACKGROUNDCOLOR + 70 +0 + 0 +APPID + 5 +31 +330 +3 +100 +AcDbSymbolTableRecord +100 +AcDbRegAppTableRecord + 2 +EZDXF + 70 +0 + 0 +ENDTAB + 0 +TABLE + 2 +DIMSTYLE + 5 +4 +330 +0 +100 +AcDbSymbolTable + 70 +1 +100 +AcDbDimStyleTable + 0 +DIMSTYLE +105 +2B +330 +4 +100 +AcDbSymbolTableRecord +100 +AcDbDimStyleTableRecord + 2 +Standard + 70 +0 + 40 +1.0 + 41 +2.5 + 42 +0.625 + 43 +3.75 + 44 +1.25 + 45 +0.0 + 46 +0.0 + 47 +0.0 + 48 +0.0 + 49 +2.5 +140 +2.5 +141 +2.5 +142 +0.0 +143 +0.03937007874 +144 +1.0 +145 +0.0 +146 +1.0 +147 +0.625 +148 +0.0 + 69 +0 + 70 +0 + 71 +0 + 72 +0 + 73 +0 + 74 +0 + 75 +0 + 76 +0 + 77 +1 + 78 +8 + 79 +3 +170 +0 +171 +3 +172 +1 +173 +0 +174 +0 +175 +0 +176 +0 +177 +0 +178 +0 +179 +2 +271 +2 +272 +2 +273 +2 +274 +3 +275 +0 +276 +0 +277 +2 +278 +44 +279 +0 +280 +0 +281 +0 +282 +0 +283 +0 +284 +8 +285 +0 +286 +0 +288 +0 +289 +3 +290 +0 +371 +-2 +372 +-2 + 0 +ENDTAB + 0 +TABLE + 2 +BLOCK_RECORD + 5 +9 +330 +0 +100 +AcDbSymbolTable + 70 +2 + 0 +BLOCK_RECORD + 5 +17 +330 +9 +100 +AcDbSymbolTableRecord +100 +AcDbBlockTableRecord + 2 +*Model_Space +340 +1A + 70 +0 +280 +1 +281 +0 + 0 +BLOCK_RECORD + 5 +1B +330 +9 +100 +AcDbSymbolTableRecord +100 +AcDbBlockTableRecord + 2 +*Paper_Space +340 +1E + 70 +0 +280 +1 +281 +0 + 0 +ENDTAB + 0 +ENDSEC + 0 +SECTION + 2 +BLOCKS + 0 +BLOCK + 5 +18 +330 +17 +100 +AcDbEntity + 8 +0 +100 +AcDbBlockBegin + 2 +*Model_Space + 70 +0 + 10 +0.0 + 20 +0.0 + 30 +0.0 + 3 +*Model_Space + 1 + + 0 +ENDBLK + 5 +19 +330 +17 +100 +AcDbEntity + 8 +0 +100 +AcDbBlockEnd + 0 +BLOCK + 5 +1C +330 +1B +100 +AcDbEntity + 8 +0 +100 +AcDbBlockBegin + 2 +*Paper_Space + 70 +0 + 10 +0.0 + 20 +0.0 + 30 +0.0 + 3 +*Paper_Space + 1 + + 0 +ENDBLK + 5 +1D +330 +1B +100 +AcDbEntity + 8 +0 +100 +AcDbBlockEnd + 0 +ENDSEC + 0 +SECTION + 2 +ENTITIES + 0 +LWPOLYLINE + 5 +2F +330 +17 +100 +AcDbEntity + 8 +material_stator_steel +100 +AcDbPolyline + 90 +25 + 70 +0 + 10 +0.055 + 20 +0.0 + 10 +0.0531 + 20 +0.0142 + 10 +0.0476 + 20 +0.0275 + 10 +0.0389 + 20 +0.0389 + 10 +0.0275 + 20 +0.0476 + 10 +0.0142 + 20 +0.0531 + 10 +0.0 + 20 +0.055 + 10 +-0.0142 + 20 +0.0531 + 10 +-0.0275 + 20 +0.0476 + 10 +-0.0389 + 20 +0.0389 + 10 +-0.0476 + 20 +0.0275 + 10 +-0.0531 + 20 +0.0142 + 10 +-0.055 + 20 +0.0 + 10 +-0.0531 + 20 +-0.0142 + 10 +-0.0476 + 20 +-0.0275 + 10 +-0.0389 + 20 +-0.0389 + 10 +-0.0275 + 20 +-0.0476 + 10 +-0.0142 + 20 +-0.0531 + 10 +0.0 + 20 +-0.055 + 10 +0.0142 + 20 +-0.0531 + 10 +0.0275 + 20 +-0.0476 + 10 +0.0389 + 20 +-0.0389 + 10 +0.0476 + 20 +-0.0275 + 10 +0.0531 + 20 +-0.0142 + 10 +0.055 + 20 +0.0 + 0 +ENDSEC + 0 +SECTION + 2 +OBJECTS + 0 +DICTIONARY + 5 +A +330 +0 +100 +AcDbDictionary +281 +1 + 3 +ACAD_COLOR +350 +B + 3 +ACAD_GROUP +350 +C + 3 +ACAD_LAYOUT +350 +D + 3 +ACAD_MATERIAL +350 +E + 3 +ACAD_MLEADERSTYLE +350 +F + 3 +ACAD_MLINESTYLE +350 +10 + 3 +ACAD_PLOTSETTINGS +350 +11 + 3 +ACAD_PLOTSTYLENAME +350 +12 + 3 +ACAD_SCALELIST +350 +14 + 3 +ACAD_TABLESTYLE +350 +15 + 3 +ACAD_VISUALSTYLE +350 +16 + 3 +EZDXF_META +350 +2D + 0 +DICTIONARY + 5 +B +330 +A +100 +AcDbDictionary +281 +1 + 0 +DICTIONARY + 5 +C +330 +A +100 +AcDbDictionary +281 +1 + 0 +DICTIONARY + 5 +D +330 +A +100 +AcDbDictionary +281 +1 + 3 +Model +350 +1A + 3 +Layout1 +350 +1E + 0 +DICTIONARY + 5 +E +330 +A +100 +AcDbDictionary +281 +1 + 3 +ByBlock +350 +1F + 3 +ByLayer +350 +20 + 3 +Global +350 +21 + 0 +DICTIONARY + 5 +F +330 +A +100 +AcDbDictionary +281 +1 + 3 +Standard +350 +2C + 0 +DICTIONARY + 5 +10 +330 +A +100 +AcDbDictionary +281 +1 + 3 +Standard +350 +22 + 0 +DICTIONARY + 5 +11 +330 +A +100 +AcDbDictionary +281 +1 + 0 +ACDBDICTIONARYWDFLT + 5 +12 +330 +A +100 +AcDbDictionary +281 +1 + 3 +Normal +350 +13 +100 +AcDbDictionaryWithDefault +340 +13 + 0 +ACDBPLACEHOLDER + 5 +13 +330 +12 + 0 +DICTIONARY + 5 +14 +330 +A +100 +AcDbDictionary +281 +1 + 0 +DICTIONARY + 5 +15 +330 +A +100 +AcDbDictionary +281 +1 + 0 +DICTIONARY + 5 +16 +330 +A +100 +AcDbDictionary +281 +1 + 0 +LAYOUT + 5 +1A +330 +D +100 +AcDbPlotSettings + 1 + + 4 +A3 + 6 + + 40 +7.5 + 41 +20.0 + 42 +7.5 + 43 +20.0 + 44 +420.0 + 45 +297.0 + 46 +0.0 + 47 +0.0 + 48 +0.0 + 49 +0.0 +140 +0.0 +141 +0.0 +142 +1.0 +143 +1.0 + 70 +1024 + 72 +1 + 73 +0 + 74 +5 + 7 + + 75 +16 + 76 +0 + 77 +2 + 78 +300 +147 +1.0 +148 +0.0 +149 +0.0 +100 +AcDbLayout + 1 +Model + 70 +1 + 71 +0 + 10 +0.0 + 20 +0.0 + 11 +420.0 + 21 +297.0 + 12 +0.0 + 22 +0.0 + 32 +0.0 + 14 +1e+20 + 24 +1e+20 + 34 +1e+20 + 15 +-1e+20 + 25 +-1e+20 + 35 +-1e+20 +146 +0.0 + 13 +0.0 + 23 +0.0 + 33 +0.0 + 16 +1.0 + 26 +0.0 + 36 +0.0 + 17 +0.0 + 27 +1.0 + 37 +0.0 + 76 +1 +330 +17 + 0 +LAYOUT + 5 +1E +330 +D +100 +AcDbPlotSettings + 1 + + 4 +A3 + 6 + + 40 +7.5 + 41 +20.0 + 42 +7.5 + 43 +20.0 + 44 +420.0 + 45 +297.0 + 46 +0.0 + 47 +0.0 + 48 +0.0 + 49 +0.0 +140 +0.0 +141 +0.0 +142 +1.0 +143 +1.0 + 70 +0 + 72 +1 + 73 +0 + 74 +5 + 7 + + 75 +16 + 76 +0 + 77 +2 + 78 +300 +147 +1.0 +148 +0.0 +149 +0.0 +100 +AcDbLayout + 1 +Layout1 + 70 +1 + 71 +1 + 10 +0.0 + 20 +0.0 + 11 +420.0 + 21 +297.0 + 12 +0.0 + 22 +0.0 + 32 +0.0 + 14 +1e+20 + 24 +1e+20 + 34 +1e+20 + 15 +-1e+20 + 25 +-1e+20 + 35 +-1e+20 +146 +0.0 + 13 +0.0 + 23 +0.0 + 33 +0.0 + 16 +1.0 + 26 +0.0 + 36 +0.0 + 17 +0.0 + 27 +1.0 + 37 +0.0 + 76 +1 +330 +1B + 0 +MATERIAL + 5 +1F +102 +{ACAD_REACTORS +330 +E +102 +} +330 +E +100 +AcDbMaterial + 1 +ByBlock + 2 + + 70 +0 + 40 +1.0 + 71 +1 + 41 +1.0 + 91 +-1023410177 + 42 +1.0 + 72 +1 + 3 + + 73 +1 + 74 +1 + 75 +1 + 44 +0.5 + 73 +0 + 45 +1.0 + 46 +1.0 + 77 +1 + 4 + + 78 +1 + 79 +1 +170 +1 + 48 +1.0 +171 +1 + 6 + +172 +1 +173 +1 +174 +1 +140 +1.0 +141 +1.0 +175 +1 + 7 + +176 +1 +177 +1 +178 +1 +143 +1.0 +179 +1 + 8 + +270 +1 +271 +1 +272 +1 +145 +1.0 +146 +1.0 +273 +1 + 9 + +274 +1 +275 +1 +276 +1 + 42 +1.0 + 72 +1 + 3 + + 73 +1 + 74 +1 + 75 +1 + 94 +63 + 0 +MATERIAL + 5 +20 +102 +{ACAD_REACTORS +330 +E +102 +} +330 +E +100 +AcDbMaterial + 1 +ByLayer + 2 + + 70 +0 + 40 +1.0 + 71 +1 + 41 +1.0 + 91 +-1023410177 + 42 +1.0 + 72 +1 + 3 + + 73 +1 + 74 +1 + 75 +1 + 44 +0.5 + 73 +0 + 45 +1.0 + 46 +1.0 + 77 +1 + 4 + + 78 +1 + 79 +1 +170 +1 + 48 +1.0 +171 +1 + 6 + +172 +1 +173 +1 +174 +1 +140 +1.0 +141 +1.0 +175 +1 + 7 + +176 +1 +177 +1 +178 +1 +143 +1.0 +179 +1 + 8 + +270 +1 +271 +1 +272 +1 +145 +1.0 +146 +1.0 +273 +1 + 9 + +274 +1 +275 +1 +276 +1 + 42 +1.0 + 72 +1 + 3 + + 73 +1 + 74 +1 + 75 +1 + 94 +63 + 0 +MATERIAL + 5 +21 +102 +{ACAD_REACTORS +330 +E +102 +} +330 +E +100 +AcDbMaterial + 1 +Global + 2 + + 70 +0 + 40 +1.0 + 71 +1 + 41 +1.0 + 91 +-1023410177 + 42 +1.0 + 72 +1 + 3 + + 73 +1 + 74 +1 + 75 +1 + 44 +0.5 + 73 +0 + 45 +1.0 + 46 +1.0 + 77 +1 + 4 + + 78 +1 + 79 +1 +170 +1 + 48 +1.0 +171 +1 + 6 + +172 +1 +173 +1 +174 +1 +140 +1.0 +141 +1.0 +175 +1 + 7 + +176 +1 +177 +1 +178 +1 +143 +1.0 +179 +1 + 8 + +270 +1 +271 +1 +272 +1 +145 +1.0 +146 +1.0 +273 +1 + 9 + +274 +1 +275 +1 +276 +1 + 42 +1.0 + 72 +1 + 3 + + 73 +1 + 74 +1 + 75 +1 + 94 +63 + 0 +MLINESTYLE + 5 +22 +102 +{ACAD_REACTORS +330 +10 +102 +} +330 +10 +100 +AcDbMlineStyle + 2 +Standard + 70 +0 + 3 + + 62 +256 + 51 +90.0 + 52 +90.0 + 71 +2 + 49 +0.5 + 62 +256 + 6 +BYLAYER + 49 +-0.5 + 62 +256 + 6 +BYLAYER + 0 +MLEADERSTYLE + 5 +2C +102 +{ACAD_REACTORS +330 +F +102 +} +330 +F +100 +AcDbMLeaderStyle +179 +2 +170 +2 +171 +1 +172 +0 + 90 +2 + 40 +0.0 + 41 +0.0 +173 +1 + 91 +-1056964608 + 92 +-2 +290 +1 + 42 +2.0 +291 +1 + 43 +8.0 + 3 +Standard + 44 +4.0 +300 + +342 +29 +174 +1 +175 +1 +176 +0 +178 +1 + 93 +-1056964608 + 45 +4.0 +292 +0 +297 +0 + 46 +4.0 + 94 +-1056964608 + 47 +1.0 + 49 +1.0 +140 +1.0 +294 +1 +141 +0.0 +177 +0 +142 +1.0 +295 +0 +296 +0 +143 +3.75 +271 +0 +272 +9 +273 +9 + 0 +DICTIONARY + 5 +2D +330 +A +100 +AcDbDictionary +280 +1 +281 +1 + 3 +CREATED_BY_EZDXF +350 +2E + 3 +WRITTEN_BY_EZDXF +350 +32 + 0 +DICTIONARYVAR + 5 +2E +330 +2D +100 +DictionaryVariables +280 +0 + 1 +1.4.3 @ 2025-11-09T21:04:00.640583+00:00 + 0 +DICTIONARYVAR + 5 +32 +330 +2D +100 +DictionaryVariables +280 +0 + 1 +1.4.3 @ 2025-11-09T21:04:00.646864+00:00 + 0 +ENDSEC + 0 +EOF diff --git a/docs/developer-guide/gui_e2e_testing.md b/docs/developer-guide/gui_e2e_testing.md new file mode 100644 index 0000000..7da41c9 --- /dev/null +++ b/docs/developer-guide/gui_e2e_testing.md @@ -0,0 +1,46 @@ +# GUI end-to-end testing + +This guide explains how to exercise the Flask GUI in a real browser using +Playwright. The suite drives the CAD upload, scenario load, and run workflow so +regressions in the interactive flow are caught early. + +## What is covered + +The Playwright test in `tests/e2e/test_gui_e2e.py` replicates the induction +motor walkthrough: + +1. Start a clean project. +2. Upload domain and rotor-bar DXFs, mark the layers as included, and save the + mapping so the combined preview refreshes. +3. Upload `inputs/induction_gui_demo.json` in **Simulation setup**. +4. Click **Run simulation** and watch the progress/log cards populate. +5. Confirm the downloads list exposes the staged field map and the + visualisation panel renders the frame. + +The test uses the template `data-testid` hooks to keep selectors stable even if +the layout evolves. + +## Environment setup + +Run the helper script to install the browser automation dependencies and the +GUI stack: + +```bash +./scripts/setup_gui_e2e_env.sh +``` + +This installs Flask, Matplotlib, ezdxf, pytest, `pytest-playwright`, and the +Playwright Chromium binary so CI or local runs have everything required. + +## Running the suite + +Launch the Playwright-driven test from the repository root: + +```bash +pytest tests/e2e/test_gui_e2e.py +``` + +The fixture spins up the Flask app on a random local port, stubs the solver +with a fast in-process field-map emission, and exercises the UI in a headless +Chromium instance. Screenshots can be added to the test if debugging future +regressions. diff --git a/docs/developer-guide/implementation/AGENTS.md b/docs/developer-guide/implementation/AGENTS.md new file mode 100644 index 0000000..a9eb1a0 --- /dev/null +++ b/docs/developer-guide/implementation/AGENTS.md @@ -0,0 +1,14 @@ +# Implementation Progress Docs Notes + +- This folder hosts running logs that track implementation efforts for major + epics (solver, GUI, etc.). Keep entries factual and date-agnostic so the + narrative remains evergreen. +- When you land new capabilities, add concise bullets under the relevant + sections rather than rewriting history; older entries provide context for + future contributors. +- If you introduce a new epic, create a sibling progress file and reference it + from the MkDocs navigation (`mkdocs.yml`). +- Tests or code changes that the documentation references should be cited with + file paths or module names where possible. +- Avoid embedding binary artefacts or screenshots here—link to reproducible + commands or test cases instead. diff --git a/docs/developer-guide/implementation/flask-gui-progress.md b/docs/developer-guide/implementation/flask-gui-progress.md index 85dfc38..9419d52 100644 --- a/docs/developer-guide/implementation/flask-gui-progress.md +++ b/docs/developer-guide/implementation/flask-gui-progress.md @@ -22,15 +22,46 @@ on the GUI-specific milestones. endpoint payloads without invoking the real solver binary. - Documented usage (`docs/user-guide/gui_flask.md`) and linked the new page into the MkDocs navigation. +- **Stage 1 UI overhaul**: + - Split the main page into dedicated geometry, control, progress, downloads, + and visualisation panels with placeholders for future DXF/animation work. + - Introduced a CAD-style top bar and workflow sidebar so navigation feels + closer to desktop tooling while leaving hooks for future menus. + - Added geometry preview rendering on demand plus live in-process and final + field-map visualisation streamed via SSE. + - Introduced configurable plotting controls (vector scaling, density, + overlays, logarithmic modes) that regenerate Matplotlib renders on request. + - Persisted scenario uploads as per-session projects, enabling preview and run + actions without re-uploading; added File-menu routes for exporting or + clearing the workspace. + - Expanded automated coverage for preview flows, solver output detection, + project reuse, and the visualisation endpoint to keep regression protection + high. + - Added helper scripts (`scripts/setup_gui_env.sh` and + `scripts/maintain_gui_env.sh`) so contributors can bootstrap dependencies, + prune artefacts, and monitor Python package freshness locally. +- Added a DXF workspace with multi-file uploads, layer assignment, combined + previews, and scenario-to-DXF export support; introduced `python/gui/dxf_utils.py` + to encapsulate parsing and rendering logic and exercised the flow in + `tests/test_gui_flask.py`. +- Hardened field-map discovery so relative solver outputs resolve against the + runtime working directory, streamed intermediate frames into a playback + gallery with animation controls, and published an end-to-end induction motor + workflow (scenario, DXFs, documentation) to guide future additions. +- Added a Playwright-driven E2E harness mirroring the induction walkthrough so + DXF uploads, scenario runs, and downloads/visuals stay covered in a real + browser session. ## In-flight / next steps -- DXF ingestion is still stubbed. Once the geometry-to-scenario converter is - ready we can replace the placeholder guard in `/upload` with real processing. +- Broaden DXF support beyond lines/polylines/circles (splines, blocks) and add + utilities that map layer selections back into scenario JSON automatically. - The progress parser currently looks for percentage tokens in stdout; refining this to understand the solver's exact log format will improve the progress bar fidelity. -- Consider persisting additional solver outputs (e.g., field map CSVs) to the - downloads list once the CLI contract is finalised. +- Surface more solver artefacts (line probes, streamlines) alongside field maps + once those exporters are exercised in real scenarios. - Harden long-running process management for multiple users (per-session queues instead of global state) if the GUI graduates beyond a single-user demo. +- Add screenshot-driven regression tests for the Bootstrap layout when we wire + up a lightweight browser harness. diff --git a/docs/user-guide/gui-workflows/induction-demo.md b/docs/user-guide/gui-workflows/induction-demo.md new file mode 100644 index 0000000..3e57a60 --- /dev/null +++ b/docs/user-guide/gui-workflows/induction-demo.md @@ -0,0 +1,112 @@ +# Induction motor GUI workflow + +This walkthrough demonstrates how to assemble the bundled three-phase induction +motor demo inside the Flask GUI, run the transient solve, and review the +resulting field maps and animation. It assumes you have already built +`motor_sim` and started the Flask server as described in the +[GUI overview](../gui_flask.md). + +## Assets + +| Purpose | File | Notes | +| --- | --- | --- | +| Scenario JSON | [`induction_gui_demo.json`](https://raw.githubusercontent.com/lpurdy01/mag_sim/main/inputs/induction_gui_demo.json) | Defines the transient spin-up with field-map, VTK series, and probe outputs. | +| Domain outline | [`docs/assets/dxf/induction_demo/domain.dxf`](../../assets/dxf/induction_demo/domain.dxf) | Axis-aligned boundary for the rectangular workspace. | +| Stator laminations | [`docs/assets/dxf/induction_demo/stator_only.dxf`](../../assets/dxf/induction_demo/stator_only.dxf) | Single layer containing the stator steel outline. | +| Rotor back-iron | [`docs/assets/dxf/induction_demo/rotor_core.dxf`](../../assets/dxf/induction_demo/rotor_core.dxf) | Rotor core polygon. | +| Rotor bars | [`docs/assets/dxf/induction_demo/rotor_bars.dxf`](../../assets/dxf/induction_demo/rotor_bars.dxf) | Conductor bars, one polyline per bar. | +| Air references | [`docs/assets/dxf/induction_demo/air_regions.dxf`](../../assets/dxf/induction_demo/air_regions.dxf) | Convenience overlay to confirm the air gap and slot cavities. | + +> Tip: if you tweak the scenario and want to regenerate the DXF set, choose +> **File → Export DXF set…** after the run. The GUI will emit fresh DXFs for the +> loaded scenario using the same layering rules described above. + +## Step 1 – Prime the workspace + +1. Launch the server with `python -m python.gui.app_flask` and open the + interface in your browser. +2. In the **File** menu choose **New project** if a previous session is still + loaded. +3. Download the assets listed above (or keep the repository checked out so the + relative links work directly). + +### Action checklist + +The automated end-to-end test mirrors these user-visible steps: + +1. Start a clean project (File → New project). +2. Import `domain.dxf` and `rotor_bars.dxf` in the **CAD workspace**. +3. Tick the imported layer rows and click **Update mapping** so the combined + preview reflects the new selections. +4. Upload `inputs/induction_gui_demo.json` in **Simulation setup**. +5. Click **Run simulation** and wait for the log to show “Simulation complete.” +6. Confirm the **Downloads** list exposes the field-map artefacts and the + **Results visualisation** panel renders the frame. + +## Step 2 – Import CAD layers + +Use the **CAD workspace** panel to upload each DXF. The recommended order helps +keep the combined preview tidy, but the GUI will happily accept any sequence. + +| Upload order | Layer selection | Suggested category | +| --- | --- | --- | +| `domain.dxf` | Single `domain` layer | `domain` | +| `stator_only.dxf` | Entire layer | `material` (stator steel) | +| `rotor_core.dxf` | Entire layer | `material` (rotor core) | +| `rotor_bars.dxf` | Entire layer | `wire` | +| `air_regions.dxf` | Optional visual guide | `structural` | + +After each upload, expand the card, tick the `Include` checkboxes, and assign +categories from the drop-down. Click **Update mapping** to persist the +selection, then confirm the coloured overlay in the **Combined preview** on the +right. When everything is aligned the stator/rotor teeth should nest cleanly +within the domain boundary. + +## Step 3 – Load the scenario + +1. Scroll to **Simulation setup** and use **Browse…** to select + `inputs/induction_gui_demo.json`. +2. Leave the solver defaults (CG, `1e-6` tolerance, `20000` max iterations) + intact. The scenario already requests the `induction_motor_field` field-map + along with the VTK series and probes, so the `--outputs` box can remain + blank. +3. Click **Run simulation**. The progress card and live log will appear and the + **Run** button will grey out while the solver works. + +## Step 4 – Monitor progress and animation + +- The live log shows each timeline frame as it converges. When the solver writes + a field map (e.g. `Frame 0: wrote field_map 'induction_motor_field' …`) the + GUI renders a preview immediately. +- The **Results & visuals** card keeps a gallery of these frames. Use the + **Play** button to loop through the snapshots, **Prev/Next** to step manually, + or drag the slider to inspect a specific frame. The caption updates with the + frame index so you can correlate it with the log. +- You can re-render the final field map with different overlays at any time by + adjusting the controls (vector scaling, log floor, streamlines) and clicking + **Update visualisation**. + +## Step 5 – Collect artefacts + +When the run completes successfully the **Downloads** list exposes: + +- The uploaded scenario JSON (with any edits from the DXF workspace preserved). +- The solver log. +- `outputs/induction_motor_field.csv` plus the other CSV/VTP artefacts declared + in the scenario. +- A PNG of the default field-map render. + +These files, along with the animation controls, are enough to document the +induction motor run or to feed the CSVs into the downstream plotting tools such +as `python/animate_three_phase.py` if you need a high-resolution movie. + +## Next steps + +- Save the current project through **File → Save scenario…** if you tweak the + geometry or solver settings. The saved JSON can be re-uploaded later to keep + iterating. +- Use **File → Export DXF set…** to share the geometry with teammates or to + seed another CAD package. +- For heavier visualisations (rotor slot colouring, full GIF exports) hand the + downloads to the existing scripts in `python/`—the GUI keeps the workspace + clean while providing a quick feedback loop. diff --git a/docs/user-guide/gui-workflows/iron-ring-demo.md b/docs/user-guide/gui-workflows/iron-ring-demo.md new file mode 100644 index 0000000..56e8cdd --- /dev/null +++ b/docs/user-guide/gui-workflows/iron-ring-demo.md @@ -0,0 +1,135 @@ +# Iron ring GUI workflow + +This walkthrough shows how to recreate and run the bundled +[`iron_ring_demo.json`](https://raw.githubusercontent.com/mag-co/mag_sim/main/inputs/iron_ring_demo.json) +scenario directly from the Flask GUI. It focuses on three goals: + +1. Importing DXF geometry (domain, iron body, windings) and keeping the layers + organised even when your CAD exporter splits stator, rotor, and coil data + into separate files. +2. Building the scenario JSON graphically via the **Materials & regions**, + **Windings**, and **Timeline** stages so you can tweak permeability and + stimulus settings without leaving the browser. +3. Launching the solve, reviewing the automatically generated field-map frames, + and downloading both the JSON and DXF exports for future iterations. + +> **Tip:** The interface lives at +> . The workflow +> pages (including this one) render on the same documentation site so you can +> open them next to the GUI while you work. + +## Prerequisites + +- A `motor_sim` build in `./build/motor_sim`. +- The GUI dependencies installed via `./scripts/setup_gui_env.sh`. +- DXF exports of your ring: either a single all-in-one file such as + `custom_target_v0.dxf` or multiple files (domain, iron, windings). Upload in + the order shown below so the combined preview layers stack predictably. + +| Upload order | Example layers | Recommended category | +| ------------ | ------------------------------------ | -------------------- | +| 1 | `DOMAIN`, `REFERENCE` | Domain boundary | +| 2 | `IRON_RING`, `CORE`, `BACKIRON` | Material region | +| 3 | `AIR_GAP`, `SLOT_AIR` (if exported) | Material region | +| 4 | `COIL_A+`, `COIL_A-`, `COIL_B+`, … | Conductor path | + +If your CAD software emits one file per subsystem (stator, rotor, windings), +import the stator/core first, then any air-gap cut-outs, and finally the wire +sets. Every upload can mix layers from different categories—you simply map each +layer to the desired role via the table in the CAD workspace. + +## Step 1 – Import and map DXF layers + +1. Open the GUI and use the **File → New project** menu entry to start fresh. +2. In the **CAD workspace**, upload your domain DXF. Keep the layer selected and + set its category to **Domain boundary**. Click **Update mapping** to redraw + the combined preview. +3. Upload the iron body DXF (or the same file again if everything lives in + `custom_target_v0.dxf`). Mark the iron layer(s) as **Material region** and + deselect any helper layers you do not want in the simulation. +4. Upload the winding DXF(s) last. Mark each conductor layer as + **Conductor path** so the **Windings** stage can reference them later. +5. Refresh the combined preview after each change to confirm the stack order + looks correct (domain outline, iron ring, then the copper overlays). + +## Step 2 – Define the material palette + +Switch to **Materials & regions**. The iron ring demo only needs two entries, +matching the JSON snippet below: + +```json +"materials": [ + {"name": "air", "mu_r": 1.0}, + {"name": "iron", "mu_r": 1000.0} +] +``` + +The GUI ships with `air` and `steel` placeholders; rename the second row to +`iron` and adjust the permeability (or add extra rows for laminations, +aluminium, etc.). Click **Save materials** to persist the palette. The DXF layer +category pickers immediately use the updated names. + +## Step 3 – Map conductor layers to windings + +Open the **Windings & conductor mapping** stage. Each row becomes a +`current_region` in the scenario JSON. + +1. Click **Add winding** until you have a row for each phase (e.g. Phase A, B, + C). Name the row, set the phase ID, and leave turns/fill at their defaults or + edit them to match your stack-up. +2. Use the wire-layer multi-select to assign the DXF layers you tagged as + **Conductor path**. Hold Ctrl/Cmd to pick both the positive and negative + sides of a phase if they were exported as separate layers. +3. Choose the orientation so clockwise windings are “into” the page and + counter-clockwise windings are “out”. Orientation simply multiplies the + generated `current_region` orientation value. +4. Click **Apply windings**. The GUI rasterises the DXF geometry, rewrites the + `sources` array, and stores the winding configuration in the project. Use + **File → Save scenario…** at any time to download the JSON with the generated + current regions. + +## Step 4 – Configure the drive timeline + +The iron ring demo uses six DC wires, so you can either keep the default static +setup (leave the timeline empty) or experiment with a transient drive: + +- **Balanced three-phase:** Set the peak current (e.g. 30 A), frequency (e.g. + 50 Hz), steps per cycle, and the phase sequence that matches your winding + order. Click **Save timeline** and the GUI will generate a `timeline` array + plus matching `transient.dt`/`n_steps` fields. +- **Manual JSON:** Paste the frames produced by a script or spreadsheet. Each + entry must include a time stamp `t` and a `phase_currents` mapping. + +The alert inside the card summarises both modes and links back to this page if +you need to cross-reference while editing. + +## Step 5 – Run, animate, and download + +1. In **Simulation setup**, either upload `iron_ring_demo.json` or click + **Save scenario…** and re-upload the project’s JSON. Pick the solver (`cg` is + typical for this case), tolerance, and iteration cap. +2. Click **Run simulation**. The log and progress panels stream updates; every + `field_map` output reported by the solver automatically lands in the + **Results visualisation** gallery. +3. Use the playback controls to loop the frames or drag the slider to inspect a + single snapshot. The **Update visualisation** form regenerates the PNG with + different arrow density, colour scales, or overlays without rerunning the + solver. +4. Download artefacts from the **Results & downloads** panel. The solver log, + field-map CSVs, DXF exports, and the project JSON are staged into + `python/gui/results/` so the **Download** buttons work even when the original + files lived elsewhere. + +## Troubleshooting checklist + +- If the windings form is empty, ensure at least one DXF layer is marked as + **Conductor path** in the CAD workspace. +- If the timeline inputs are disabled, upload or preview a scenario first so + the project has somewhere to store the generated frames. +- When a solver log reports `outputs/iron_ring_field.csv` but the gallery stays + empty, confirm the CSV exists under the repository’s `outputs/` directory. The + GUI resolves relative paths against both the scenario folder and the solver’s + working directory, so a simple `ls outputs` is often enough to confirm. +- Use **File → New project** to reset the workspace if you want to experiment + with a different DXF set without carrying over the previous windings or + timeline. diff --git a/docs/user-guide/gui_flask.md b/docs/user-guide/gui_flask.md index 3792f3a..6111c6c 100644 --- a/docs/user-guide/gui_flask.md +++ b/docs/user-guide/gui_flask.md @@ -7,10 +7,10 @@ prototype while staying close to the underlying command-line workflow. ## Requirements - A built `motor_sim` executable in `./build/motor_sim`. -- The devcontainer (or local virtual environment) must include Flask and its - lightweight companions. The repository's `.devcontainer/Dockerfile` installs - Flask, Flask-CORS, NumPy, Matplotlib, and `ezdxf` so the GUI has everything it - needs when launched inside Codespaces. +- Flask, Matplotlib, NumPy, and ezdxf available in the active Python + environment. Execute `./scripts/setup_gui_env.sh` to install the toolchain + (and create the GUI runtime folders) when a pre-provisioned devcontainer is + not available. ## Starting the server @@ -29,30 +29,111 @@ prefer Flask's CLI wrapper. ## Using the interface -1. Upload a scenario JSON file via the **Scenario file** input. (DXF uploads are - reserved for a future update and currently return a validation error.) -2. Pick a solver (`cg` or `sor`), adjust the tolerance and maximum iteration - count if required, and optionally pass a value for `--outputs`. -3. Click **Run simulation**. The form disables itself, the **Stop** button - becomes available, and a progress card appears. -4. The server streams the solver's stdout via Server-Sent Events (SSE). The - progress bar reacts to any percentage tokens in the log, and the log panel - fills with the live output. -5. When the run finishes, the log persists and a **Downloads** card appears with - links to the uploaded scenario and the captured log file. The log is useful - for sharing solver traces without leaving the browser. -6. Click **Run simulation** again to process another scenario. Only one solve is - allowed at a time; the interface reports an error if you attempt to start a - second run before the first completes. - -Use the **Stop** button to request early termination. The server sends -`terminate()` to the subprocess and the final log entry marks the stop request. -Depending on solver state, expect a short delay while the child process exits. +The CAD-inspired layout splits the page into a top navigation bar, a workflow +sidebar, and content panels for each stage. The **File** menu exposes +project-wide actions (**New project** clears the workspace, **Save scenario…** +downloads the current JSON, **Export DXF set…** produces layered DXFs for the +current scenario), while the sidebar links jump to the key panels without +leaving the page. + +Looking for a guided example? Follow the hosted walkthroughs: + +- [Induction motor GUI workflow](https://lpurdy01.github.io/mag_sim/user-guide/gui-workflows/induction-demo/) + – multi-file DXF import and transient animation. +- [Iron ring GUI workflow](https://lpurdy01.github.io/mag_sim/user-guide/gui-workflows/iron-ring-demo/) + – mapping custom DXF layers to materials, windings, and timelines. + +Work through the panels in order: + +1. **CAD workspace:** Import DXF files, mark each layer with a category (domain, + material, magnet, conductor), and update the combined preview. Upload domain + references first, then materials/magnets, and finally wire layers. Each file + can contain multiple categories; the form lets you toggle inclusion per + layer. +2. **Materials & regions:** Define the permeability palette that the scenario’s + `materials` block uses. The table feeds the DXF layer drop-downs so your CAD + mapping stays in sync with the JSON. +3. **Windings & conductor mapping:** Create one row per phase/coil group, + configure turns/fill/orientation, and assign the conductor layers that were + tagged in the CAD workspace. Saving the form rasterises the DXF geometry into + `current_region` entries and stores the winding metadata in the project. +4. **Timeline designer:** Generate balanced three-phase drive patterns or paste + manual timeline JSON. The GUI updates both the `timeline` array and the + `transient` parameters so subsequent CLI runs remain consistent. +5. **Geometry preview:** Render the scenario’s domain/magnets without solving to + confirm the JSON matches the CAD stack-up. +6. **Simulation setup:** Upload or reuse the scenario JSON, tweak solver + settings (`cg`/`sor`, tolerance, iteration cap, `--outputs`), and launch the + solve. Use the alert inside the card as a reminder that the sections above + control the graphical scenario composer. +7. **Progress, results, and downloads:** Watch the SSE-driven progress bar and + log, review the live field-map gallery, and download the scenario/log/DXF + exports/field-map CSVs that the run produced. + +Only one solve is allowed at a time; the interface reports an error if you try +to launch a second run before the first finishes. Use **Stop** to request +termination—`terminate()` is sent to the subprocess and the log records the +request before the child exits. + +### Environment upkeep + +Generated artefacts accumulate under `python/gui/uploads` and +`python/gui/results`. Run `./scripts/maintain_gui_env.sh` periodically (set +`KEEP_DAYS` or pass a day count to keep more history) to prune stale files and +surface any outdated Python packages. + +### Visualisation controls + +The results card includes a lightweight form for regenerating the field-map +image without leaving the browser: + +- **Vector scaling**: switch between linear arrows, logarithmic scaling, or hide + vectors entirely. +- **Colour scale**: toggle between linear and logarithmic magnitude mapping. +- **Arrow skip**: control the quiver density (defaults to every fourth sample). +- **Log floor / Vector floor**: clamp low values when using logarithmic modes. +- **Region outlines / Streamlines**: enable or disable material/magnet borders + and streamline overlays. + +Choose the desired parameters and click **Update visualisation**. The server +re-renders the image with Matplotlib and returns a fresh PNG via the +`/visualization.png` endpoint, keeping the UI responsive with minimal +JavaScript. + +During a simulation the GUI also builds a frame gallery from each reported +`field_map`. Use the playback controls above the image to loop the snapshots, +step frame-by-frame, or drag the slider to inspect a single moment. The caption +updates with the frame index, making it easy to cross-reference the solver log +without leaving the page. + +### Materials, windings, and timeline composer + +The three new stages share the same persistence model: + +- Saving the **Materials** form rewrites the `materials` array in the current + project JSON and immediately feeds those entries back into the DXF layer + pickers. +- The **Windings** form stores a project-local winding list and generates + `current_region` sources by rasterising the selected DXF layers. Every selected + layer can be reused across multiple windings (the UI simply records the + token). Orientation switches flip the sign of the generated `orientation` + field. +- The **Timeline** designer stores both the generated frames and the inputs used + to create them so you can revisit the balanced parameters or edit the raw JSON + later. + +Whenever you change any of these composer stages, use **File → Save scenario…** +to export the updated JSON, or simply press **Run simulation** to drive +`motor_sim` with the newly generated spec. ## Known limitations -- DXF geometry ingestion is stubbed; use JSON scenarios generated via the - existing Python helpers until the converter is available. +- DXF ingestion currently supports LINE/POLYLINE/LWPOLYLINE and CIRCLE entities. + Splines, text, and block references are ignored. Use your CAD tool to explode + complex geometry before uploading. +- Preview and visualisation rendering rely on Matplotlib, NumPy, and ezdxf. + These dependencies ship with the devcontainer, but a custom environment must + provide the same stack. - The progress parser uses a simple percentage heuristic. If the solver log does not emit percentage tokens the bar will stay at 0 % until the run completes. - The current prototype keeps global state and targets single-user usage. A diff --git a/inputs/induction_gui_demo.json b/inputs/induction_gui_demo.json new file mode 100644 index 0000000..3fd3a4a --- /dev/null +++ b/inputs/induction_gui_demo.json @@ -0,0 +1,1389 @@ +{ + "version": "0.2", + "units": "SI", + "domain": { + "Lx": 0.14, + "Ly": 0.14, + "nx": 65, + "ny": 65 + }, + "boundary": { + "type": "neumann" + }, + "materials": [ + { + "name": "air", + "mu_r": 1.0, + "sigma": 0.0 + }, + { + "name": "stator_steel", + "mu_r": 600.0, + "sigma": 0.0 + }, + { + "name": "rotor_core", + "mu_r": 400.0, + "sigma": 500000.0 + }, + { + "name": "rotor_bar", + "mu_r": 1.0, + "sigma": 18000000.0 + } + ], + "regions": [ + { + "type": "uniform", + "material": "air" + }, + { + "type": "polygon", + "material": "stator_steel", + "vertices": [ + [ + 0.055, + 0.0 + ], + [ + 0.0531, + 0.0142 + ], + [ + 0.0476, + 0.0275 + ], + [ + 0.0389, + 0.0389 + ], + [ + 0.0275, + 0.0476 + ], + [ + 0.0142, + 0.0531 + ], + [ + 0.0, + 0.055 + ], + [ + -0.0142, + 0.0531 + ], + [ + -0.0275, + 0.0476 + ], + [ + -0.0389, + 0.0389 + ], + [ + -0.0476, + 0.0275 + ], + [ + -0.0531, + 0.0142 + ], + [ + -0.055, + 0.0 + ], + [ + -0.0531, + -0.0142 + ], + [ + -0.0476, + -0.0275 + ], + [ + -0.0389, + -0.0389 + ], + [ + -0.0275, + -0.0476 + ], + [ + -0.0142, + -0.0531 + ], + [ + 0.0, + -0.055 + ], + [ + 0.0142, + -0.0531 + ], + [ + 0.0275, + -0.0476 + ], + [ + 0.0389, + -0.0389 + ], + [ + 0.0476, + -0.0275 + ], + [ + 0.0531, + -0.0142 + ] + ] + }, + { + "type": "polygon", + "material": "air", + "vertices": [ + [ + 0.0395, + -0.0063 + ], + [ + 0.0494, + -0.0078 + ], + [ + 0.0494, + 0.0078 + ], + [ + 0.0395, + 0.0063 + ] + ] + }, + { + "type": "polygon", + "material": "air", + "vertices": [ + [ + 0.0252, + 0.0311 + ], + [ + 0.0315, + 0.0389 + ], + [ + 0.0179, + 0.0467 + ], + [ + 0.0143, + 0.0373 + ] + ] + }, + { + "type": "polygon", + "material": "air", + "vertices": [ + [ + -0.0143, + 0.0373 + ], + [ + -0.0179, + 0.0467 + ], + [ + -0.0315, + 0.0389 + ], + [ + -0.0252, + 0.0311 + ] + ] + }, + { + "type": "polygon", + "material": "air", + "vertices": [ + [ + -0.0395, + 0.0063 + ], + [ + -0.0494, + 0.0078 + ], + [ + -0.0494, + -0.0078 + ], + [ + -0.0395, + -0.0063 + ] + ] + }, + { + "type": "polygon", + "material": "air", + "vertices": [ + [ + -0.0252, + -0.0311 + ], + [ + -0.0315, + -0.0389 + ], + [ + -0.0179, + -0.0467 + ], + [ + -0.0143, + -0.0373 + ] + ] + }, + { + "type": "polygon", + "material": "air", + "vertices": [ + [ + 0.0143, + -0.0373 + ], + [ + 0.0179, + -0.0467 + ], + [ + 0.0315, + -0.0389 + ], + [ + 0.0252, + -0.0311 + ] + ] + }, + { + "type": "polygon", + "material": "air", + "vertices": [ + [ + 0.04, + 0.0 + ], + [ + 0.0376, + 0.0137 + ], + [ + 0.0306, + 0.0257 + ], + [ + 0.02, + 0.0346 + ], + [ + 0.0069, + 0.0394 + ], + [ + -0.0069, + 0.0394 + ], + [ + -0.02, + 0.0346 + ], + [ + -0.0306, + 0.0257 + ], + [ + -0.0376, + 0.0137 + ], + [ + -0.04, + 0.0 + ], + [ + -0.0376, + -0.0137 + ], + [ + -0.0306, + -0.0257 + ], + [ + -0.02, + -0.0346 + ], + [ + -0.0069, + -0.0394 + ], + [ + 0.0069, + -0.0394 + ], + [ + 0.02, + -0.0346 + ], + [ + 0.0306, + -0.0257 + ], + [ + 0.0376, + -0.0137 + ] + ] + }, + { + "type": "polygon", + "material": "rotor_core", + "vertices": [ + [ + 0.0232, + 0.0 + ], + [ + 0.0209, + 0.0101 + ], + [ + 0.0145, + 0.0181 + ], + [ + 0.0052, + 0.0226 + ], + [ + -0.0052, + 0.0226 + ], + [ + -0.0145, + 0.0181 + ], + [ + -0.0209, + 0.0101 + ], + [ + -0.0232, + 0.0 + ], + [ + -0.0209, + -0.0101 + ], + [ + -0.0145, + -0.0181 + ], + [ + -0.0052, + -0.0226 + ], + [ + 0.0052, + -0.0226 + ], + [ + 0.0145, + -0.0181 + ], + [ + 0.0209, + -0.0101 + ] + ] + }, + { + "type": "polygon", + "material": "rotor_bar", + "vertices": [ + [ + 0.0081, + -0.0008 + ], + [ + 0.0226, + -0.0024 + ], + [ + 0.0226, + 0.0024 + ], + [ + 0.0081, + 0.0008 + ] + ] + }, + { + "type": "polygon", + "material": "rotor_bar", + "vertices": [ + [ + 0.0048, + 0.0066 + ], + [ + 0.0134, + 0.0184 + ], + [ + 0.0092, + 0.0208 + ], + [ + 0.0033, + 0.0074 + ] + ] + }, + { + "type": "polygon", + "material": "rotor_bar", + "vertices": [ + [ + -0.0033, + 0.0074 + ], + [ + -0.0092, + 0.0208 + ], + [ + -0.0134, + 0.0184 + ], + [ + -0.0048, + 0.0066 + ] + ] + }, + { + "type": "polygon", + "material": "rotor_bar", + "vertices": [ + [ + -0.0081, + 0.0008 + ], + [ + -0.0226, + 0.0024 + ], + [ + -0.0226, + -0.0024 + ], + [ + -0.0081, + -0.0008 + ] + ] + }, + { + "type": "polygon", + "material": "rotor_bar", + "vertices": [ + [ + -0.0048, + -0.0066 + ], + [ + -0.0134, + -0.0184 + ], + [ + -0.0092, + -0.0208 + ], + [ + -0.0033, + -0.0074 + ] + ] + }, + { + "type": "polygon", + "material": "rotor_bar", + "vertices": [ + [ + 0.0033, + -0.0074 + ], + [ + 0.0092, + -0.0208 + ], + [ + 0.0134, + -0.0184 + ], + [ + 0.0048, + -0.0066 + ] + ] + } + ], + "magnet_regions": [], + "rotors": [ + { + "name": "induction_rotor", + "pivot": [ + 0.0, + 0.0 + ], + "polygons": [ + 8, + 9, + 10, + 11, + 12, + 13, + 14 + ] + } + ], + "sources": [ + { + "type": "current_region", + "id": "A_pos", + "label": "Phase A+", + "phase": "A", + "orientation": 1.0, + "vertices": [ + [ + 0.0395, + -0.0063 + ], + [ + 0.0494, + -0.0078 + ], + [ + 0.0494, + 0.0078 + ], + [ + 0.0395, + 0.0063 + ] + ], + "I": 0.0, + "turns": 50.0, + "fill_fraction": 0.55 + }, + { + "type": "current_region", + "id": "B_neg", + "label": "Phase B-", + "phase": "B", + "orientation": -1.0, + "vertices": [ + [ + 0.0252, + 0.0311 + ], + [ + 0.0315, + 0.0389 + ], + [ + 0.0179, + 0.0467 + ], + [ + 0.0143, + 0.0373 + ] + ], + "I": 0.0, + "turns": 50.0, + "fill_fraction": 0.55 + }, + { + "type": "current_region", + "id": "C_pos", + "label": "Phase C+", + "phase": "C", + "orientation": 1.0, + "vertices": [ + [ + -0.0143, + 0.0373 + ], + [ + -0.0179, + 0.0467 + ], + [ + -0.0315, + 0.0389 + ], + [ + -0.0252, + 0.0311 + ] + ], + "I": 0.0, + "turns": 50.0, + "fill_fraction": 0.55 + }, + { + "type": "current_region", + "id": "A_neg", + "label": "Phase A-", + "phase": "A", + "orientation": -1.0, + "vertices": [ + [ + -0.0395, + 0.0063 + ], + [ + -0.0494, + 0.0078 + ], + [ + -0.0494, + -0.0078 + ], + [ + -0.0395, + -0.0063 + ] + ], + "I": 0.0, + "turns": 50.0, + "fill_fraction": 0.55 + }, + { + "type": "current_region", + "id": "B_pos", + "label": "Phase B+", + "phase": "B", + "orientation": 1.0, + "vertices": [ + [ + -0.0252, + -0.0311 + ], + [ + -0.0315, + -0.0389 + ], + [ + -0.0179, + -0.0467 + ], + [ + -0.0143, + -0.0373 + ] + ], + "I": 0.0, + "turns": 50.0, + "fill_fraction": 0.55 + }, + { + "type": "current_region", + "id": "C_neg", + "label": "Phase C-", + "phase": "C", + "orientation": -1.0, + "vertices": [ + [ + 0.0143, + -0.0373 + ], + [ + 0.0179, + -0.0467 + ], + [ + 0.0315, + -0.0389 + ], + [ + 0.0252, + -0.0311 + ] + ], + "I": 0.0, + "turns": 50.0, + "fill_fraction": 0.55 + } + ], + "mechanical": { + "rotors": [ + { + "name": "induction_rotor", + "inertia": 0.0011, + "damping": 0.0001, + "load_torque": 0.08, + "initial_angle_deg": -20.0, + "initial_speed_rpm": 0.0, + "torque_probe": "induction_motor_torque" + } + ] + }, + "transient": { + "dt": 0.0014, + "n_steps": 36 + }, + "timeline": [ + { + "t": 0.0, + "phase_currents": { + "A": 0.0, + "B": -36.3731, + "C": 36.3731 + } + }, + { + "t": 0.0014, + "phase_currents": { + "A": 21.0, + "B": -42.0, + "C": 21.0 + } + }, + { + "t": 0.0028, + "phase_currents": { + "A": 36.3731, + "B": -36.3731, + "C": 0.0 + } + }, + { + "t": 0.0042, + "phase_currents": { + "A": 42.0, + "B": -21.0, + "C": -21.0 + } + }, + { + "t": 0.0056, + "phase_currents": { + "A": 36.3731, + "B": 0.0, + "C": -36.3731 + } + }, + { + "t": 0.0069, + "phase_currents": { + "A": 21.0, + "B": 21.0, + "C": -42.0 + } + }, + { + "t": 0.0083, + "phase_currents": { + "A": 0.0, + "B": 36.3731, + "C": -36.3731 + } + }, + { + "t": 0.0097, + "phase_currents": { + "A": -21.0, + "B": 42.0, + "C": -21.0 + } + }, + { + "t": 0.0111, + "phase_currents": { + "A": -36.3731, + "B": 36.3731, + "C": 0.0 + } + }, + { + "t": 0.0125, + "phase_currents": { + "A": -42.0, + "B": 21.0, + "C": 21.0 + } + }, + { + "t": 0.0139, + "phase_currents": { + "A": -36.3731, + "B": 0.0, + "C": 36.3731 + } + }, + { + "t": 0.0153, + "phase_currents": { + "A": -21.0, + "B": -21.0, + "C": 42.0 + } + }, + { + "t": 0.0167, + "phase_currents": { + "A": 0.0, + "B": -36.3731, + "C": 36.3731 + } + }, + { + "t": 0.0181, + "phase_currents": { + "A": 21.0, + "B": -42.0, + "C": 21.0 + } + }, + { + "t": 0.0194, + "phase_currents": { + "A": 36.3731, + "B": -36.3731, + "C": 0.0 + } + }, + { + "t": 0.0208, + "phase_currents": { + "A": 42.0, + "B": -21.0, + "C": -21.0 + } + }, + { + "t": 0.0222, + "phase_currents": { + "A": 36.3731, + "B": 0.0, + "C": -36.3731 + } + }, + { + "t": 0.0236, + "phase_currents": { + "A": 21.0, + "B": 21.0, + "C": -42.0 + } + }, + { + "t": 0.025, + "phase_currents": { + "A": 0.0, + "B": 36.3731, + "C": -36.3731 + } + }, + { + "t": 0.0264, + "phase_currents": { + "A": -21.0, + "B": 42.0, + "C": -21.0 + } + }, + { + "t": 0.0278, + "phase_currents": { + "A": -36.3731, + "B": 36.3731, + "C": 0.0 + } + }, + { + "t": 0.0292, + "phase_currents": { + "A": -42.0, + "B": 21.0, + "C": 21.0 + } + }, + { + "t": 0.0306, + "phase_currents": { + "A": -36.3731, + "B": 0.0, + "C": 36.3731 + } + }, + { + "t": 0.0319, + "phase_currents": { + "A": -21.0, + "B": -21.0, + "C": 42.0 + } + }, + { + "t": 0.0333, + "phase_currents": { + "A": 0.0, + "B": -36.3731, + "C": 36.3731 + } + }, + { + "t": 0.0347, + "phase_currents": { + "A": 21.0, + "B": -42.0, + "C": 21.0 + } + }, + { + "t": 0.0361, + "phase_currents": { + "A": 36.3731, + "B": -36.3731, + "C": 0.0 + } + }, + { + "t": 0.0375, + "phase_currents": { + "A": 42.0, + "B": -21.0, + "C": -21.0 + } + }, + { + "t": 0.0389, + "phase_currents": { + "A": 36.3731, + "B": 0.0, + "C": -36.3731 + } + }, + { + "t": 0.0403, + "phase_currents": { + "A": 21.0, + "B": 21.0, + "C": -42.0 + } + }, + { + "t": 0.0417, + "phase_currents": { + "A": 0.0, + "B": 36.3731, + "C": -36.3731 + } + }, + { + "t": 0.0431, + "phase_currents": { + "A": -21.0, + "B": 42.0, + "C": -21.0 + } + }, + { + "t": 0.0444, + "phase_currents": { + "A": -36.3731, + "B": 36.3731, + "C": 0.0 + } + }, + { + "t": 0.0458, + "phase_currents": { + "A": -42.0, + "B": 21.0, + "C": 21.0 + } + }, + { + "t": 0.0472, + "phase_currents": { + "A": -36.3731, + "B": 0.0, + "C": 36.3731 + } + }, + { + "t": 0.0486, + "phase_currents": { + "A": -21.0, + "B": -21.0, + "C": 42.0 + } + } + ], + "outputs": [ + { + "type": "field_map", + "id": "induction_motor_field", + "path": "outputs/induction_motor_field.csv" + }, + { + "type": "vtk_field_series", + "id": "induction_motor_series", + "dir": "outputs", + "basename": "induction_motor_frame", + "quantities": [ + "B", + "H" + ] + }, + { + "type": "polyline_outlines", + "id": "induction_motor_outlines", + "path": "outputs/induction_motor_outlines.vtp" + }, + { + "type": "bore_avg_B", + "id": "induction_motor_bore", + "path": "outputs/induction_motor_bore.csv", + "vertices": [ + [ + 0.026, + 0.0 + ], + [ + 0.0251, + 0.0067 + ], + [ + 0.0225, + 0.013 + ], + [ + 0.0184, + 0.0184 + ], + [ + 0.013, + 0.0225 + ], + [ + 0.0067, + 0.0251 + ], + [ + 0.0, + 0.026 + ], + [ + -0.0067, + 0.0251 + ], + [ + -0.013, + 0.0225 + ], + [ + -0.0184, + 0.0184 + ], + [ + -0.0225, + 0.013 + ], + [ + -0.0251, + 0.0067 + ], + [ + -0.026, + 0.0 + ], + [ + -0.0251, + -0.0067 + ], + [ + -0.0225, + -0.013 + ], + [ + -0.0184, + -0.0184 + ], + [ + -0.013, + -0.0225 + ], + [ + -0.0067, + -0.0251 + ], + [ + 0.0, + -0.026 + ], + [ + 0.0067, + -0.0251 + ], + [ + 0.013, + -0.0225 + ], + [ + 0.0184, + -0.0184 + ], + [ + 0.0225, + -0.013 + ], + [ + 0.0251, + -0.0067 + ] + ] + }, + { + "type": "probe", + "id": "induction_motor_torque", + "probe_type": "torque", + "method": "stress_tensor", + "loop": { + "type": "polygon", + "vertices": [ + [ + 0.0262, + 0.0 + ], + [ + 0.0256, + 0.0054 + ], + [ + 0.0239, + 0.0107 + ], + [ + 0.0212, + 0.0154 + ], + [ + 0.0175, + 0.0195 + ], + [ + 0.0131, + 0.0227 + ], + [ + 0.0081, + 0.0249 + ], + [ + 0.0027, + 0.0261 + ], + [ + -0.0027, + 0.0261 + ], + [ + -0.0081, + 0.0249 + ], + [ + -0.0131, + 0.0227 + ], + [ + -0.0175, + 0.0195 + ], + [ + -0.0212, + 0.0154 + ], + [ + -0.0239, + 0.0107 + ], + [ + -0.0256, + 0.0054 + ], + [ + -0.0262, + 0.0 + ], + [ + -0.0256, + -0.0054 + ], + [ + -0.0239, + -0.0107 + ], + [ + -0.0212, + -0.0154 + ], + [ + -0.0175, + -0.0195 + ], + [ + -0.0131, + -0.0227 + ], + [ + -0.0081, + -0.0249 + ], + [ + -0.0027, + -0.0261 + ], + [ + 0.0027, + -0.0261 + ], + [ + 0.0081, + -0.0249 + ], + [ + 0.0131, + -0.0227 + ], + [ + 0.0175, + -0.0195 + ], + [ + 0.0212, + -0.0154 + ], + [ + 0.0239, + -0.0107 + ], + [ + 0.0256, + -0.0054 + ] + ] + }, + "path": "outputs/induction_motor_torque.csv" + }, + { + "type": "back_emf_probe", + "id": "induction_motor_phase_a_emf", + "component": "Bmag", + "region": { + "type": "polygon", + "vertices": [ + [ + 0.0395, + -0.0063 + ], + [ + 0.0494, + -0.0078 + ], + [ + 0.0494, + 0.0078 + ], + [ + 0.0395, + 0.0063 + ] + ] + }, + "path": "outputs/induction_motor_phase_a_emf.csv" + }, + { + "type": "back_emf_probe", + "id": "induction_motor_phase_b_emf", + "component": "Bmag", + "region": { + "type": "polygon", + "vertices": [ + [ + -0.0252, + -0.0311 + ], + [ + -0.0315, + -0.0389 + ], + [ + -0.0179, + -0.0467 + ], + [ + -0.0143, + -0.0373 + ] + ] + }, + "path": "outputs/induction_motor_phase_b_emf.csv" + }, + { + "type": "back_emf_probe", + "id": "induction_motor_phase_c_emf", + "component": "Bmag", + "region": { + "type": "polygon", + "vertices": [ + [ + -0.0143, + 0.0373 + ], + [ + -0.0179, + 0.0467 + ], + [ + -0.0315, + 0.0389 + ], + [ + -0.0252, + 0.0311 + ] + ] + }, + "path": "outputs/induction_motor_phase_c_emf.csv" + }, + { + "type": "mechanical_trace", + "id": "induction_motor_mechanical", + "path": "outputs/induction_motor_mechanical.csv", + "rotors": [ + "induction_rotor" + ] + } + ] +} diff --git a/mkdocs.yml b/mkdocs.yml index e6a3340..4e958f7 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -85,6 +85,8 @@ nav: - User Guide: - user-guide/running-simulations.md - Web GUI (Flask): user-guide/gui_flask.md + - Induction motor GUI workflow: user-guide/gui-workflows/induction-demo.md + - Iron ring GUI workflow: user-guide/gui-workflows/iron-ring-demo.md - Machines: - user-guide/machines/three-phase-stator.md - user-guide/machines/induction.md @@ -101,6 +103,8 @@ nav: - developer-guide/implementation/status.md - developer-guide/implementation/progress.md - developer-guide/implementation/flask-gui-progress.md + - developer-guide/gui_e2e_testing.md + - developer-guide/implementation/AGENTS.md - developer-guide/implementation/validation-stages.md - developer-guide/implementation/solver-benchmarks.md - Math & Solver: diff --git a/python/gui/AGENTS.md b/python/gui/AGENTS.md index 5738e6d..56db6da 100644 --- a/python/gui/AGENTS.md +++ b/python/gui/AGENTS.md @@ -7,8 +7,34 @@ - Reuse the existing `SimulationManager` helper when wiring new routes. Ensure updates remain thread-safe and continue to enforce the single-simulation constraint. +- Stage 1 of the GUI overhaul introduced geometry previews, live field-map + visualisation, the DXF workspace, and the `/visualization.png` customisation + endpoint. The UI now includes a CAD-style top bar, workflow sidebar, and a + project-aware workspace summary. Keep the File-menu actions + (`/project/reset`, `/project/scenario`, `/project/export_dxf`) and + per-session project state intact when iterating on features. +- The results panel now exposes playback controls powered by SSE frame events. + Preserve the `sequence_index`, `frame_index`, and `frames` metadata so the + animation gallery can stay in sync with the solver log, and extend the JS + helpers (`registerFrame`, `advanceFrame`) when adding new visual streams. +- DXF handling lives in `python/gui/dxf_utils.py`; reuse its helpers when + parsing uploads, rendering previews, or exporting geometry. Layer selections + are stored in `PROJECTS[pid]['dxf_files']`—mutations should be mirrored in + tests to preserve coverage. +- Sample DXFs for the induction workflow live under + `docs/assets/dxf/induction_demo/`; keep them aligned with + `inputs/induction_gui_demo.json` and update the workflow doc when you add new + layers or assets. +- Follow-up work should expand tests when touching related code—fixtures clear + `PROJECTS`, so new project-oriented behaviour needs explicit coverage. +- End-to-end tests live under `tests/e2e/` and rely on `data-testid` hooks in + the templates. Preserve or extend those attributes when adjusting the markup + so Playwright selectors remain stable. - When modifying behaviour, update the user guide (`docs/user-guide/gui_flask.md`) and extend `tests/test_gui_flask.py` so new code paths stay covered. - Runtime artefacts such as uploads and generated logs should stay out of the repository. Use or extend the ignore rules in this directory if new folders are introduced. +- The helper scripts `scripts/setup_gui_env.sh` and + `scripts/maintain_gui_env.sh` keep local environments reproducible—run them + before hacking on the GUI or when tidying accumulated artefacts. diff --git a/python/gui/app_flask.py b/python/gui/app_flask.py index 91b532e..18f5433 100644 --- a/python/gui/app_flask.py +++ b/python/gui/app_flask.py @@ -2,13 +2,19 @@ from __future__ import annotations +import copy import json +import math import queue +import re +import shutil import subprocess import threading import time +import uuid +import zipfile from pathlib import Path -from typing import Any, Dict, List, Optional +from typing import Any, Dict, List, Optional, Set, Tuple from flask import ( Flask, @@ -17,17 +23,52 @@ redirect, render_template, request, - url_for, send_file, + session, + url_for, ) from werkzeug.utils import secure_filename +from python.gui.dxf_utils import ( + DxfError, + DxfPrimitive, + export_scenario_to_dxf, + load_primitives, + render_preview, + summarise_dxf, +) +from python.gui.render import ( + DEFAULT_LOG_FLOOR, + render_field_map_image, + render_geometry_preview, +) + ALLOWED_EXTENSIONS = {".json", ".dxf"} DEFAULT_SOLVER = "cg" DEFAULT_TOLERANCE = 1e-6 DEFAULT_MAX_ITERS = 20000 MAX_UPLOAD_SIZE_MB = 50 +DEFAULT_VECTOR_MODE = "linear" +DEFAULT_COLOR_SCALE = "linear" +DEFAULT_QUIVER_SKIP = 4 +DEFAULT_STREAMLINES = False +DEFAULT_WINDING_TURNS = 50.0 +DEFAULT_FILL_FRACTION = 0.55 +DEFAULT_PHASE_SEQUENCE = ["A", "B", "C"] +MIN_TIMELINE_STEPS = 2 +DXF_CATEGORY_OPTIONS = [ + ("domain", "Domain boundary"), + ("material", "Material region"), + ("magnet", "Magnet region"), + ("wire", "Conductor path"), + ("structural", "Structural / reference"), + ("unassigned", "Unassigned"), +] +DXF_DEFAULT_CATEGORY = "unassigned" +FIELD_MAP_REGEX = re.compile( + r"Frame\\s+(?P\\d+):\\s+wrote\\s+field_map\\s+'(?P[^']+)'\\s+to\\s+(?P.+)" +) class SimulationManager: @@ -42,6 +83,7 @@ def __init__(self, flask_app: Flask) -> None: self._running = False self._stop_requested = False self._metadata: Dict[str, Any] = {} + self._last_result: Dict[str, Any] = {} @property def is_running(self) -> bool: @@ -51,6 +93,28 @@ def is_running(self) -> bool: def queue(self) -> "queue.Queue[Dict[str, Any]]": return self._queue + def get_scenario_path(self) -> Optional[Path]: + with self._lock: + scenario = self._metadata.get("scenario_path") + if isinstance(scenario, Path): + return scenario + if isinstance(scenario, str) and scenario: + return Path(scenario) + return None + + def get_latest_field_map(self) -> Optional[Path]: + with self._lock: + latest = self._metadata.get("latest_field_map") + if isinstance(latest, Path): + return latest + if isinstance(latest, str) and latest: + return Path(latest) + return None + + def get_last_result(self) -> Dict[str, Any]: + with self._lock: + return dict(self._last_result) + def start( self, command: List[str], @@ -58,6 +122,7 @@ def start( scenario_path: Path, log_path: Path, extra_downloads: Optional[List[Dict[str, Any]]] = None, + field_outputs: Optional[List[Dict[str, Any]]] = None, ) -> None: with self._lock: if self._running: @@ -68,7 +133,12 @@ def start( self._metadata = { "scenario_path": scenario_path, "log_path": log_path, - "extra_downloads": extra_downloads or [], + "extra_downloads": list(extra_downloads or []), + "field_outputs": list(field_outputs or []), + "processed_field_maps": set(), + "process_cwd": Path.cwd(), + "rendered_frames": [], + "frame_sequence": 0, } self._queue.put({ "started": True, @@ -104,6 +174,13 @@ def reset(self) -> None: self._running = False self._stop_requested = False self._metadata = {} + self._last_result = {} + + def clear_last_result(self) -> None: + """Discard the cached visualisation payload.""" + + with self._lock: + self._last_result = {} def _drain_queue_locked(self) -> None: while True: @@ -112,6 +189,137 @@ def _drain_queue_locked(self) -> None: except queue.Empty: break + def _handle_field_map_event(self, *, frame: Optional[int], field_id: str, path_str: str) -> None: + scenario_path: Optional[Path] = self._metadata.get("scenario_path") + if scenario_path is None or not scenario_path.exists(): + return + + cleaned_path = path_str.strip().strip('"').strip("'") + if not cleaned_path: + return + + process_cwd: Optional[Path] = self._metadata.get("process_cwd") + scenario_dir = scenario_path.parent if scenario_path else None + output_path = _resolve_output_path( + Path(cleaned_path), + scenario_dir=scenario_dir, + process_cwd=process_cwd, + ) + + deadline = time.time() + 1.0 + while not output_path.exists() and time.time() < deadline: + time.sleep(0.05) + + sequence_index: Optional[int] = None + + with self._lock: + processed: Set[Path] = self._metadata.setdefault("processed_field_maps", set()) + if output_path in processed: + return + + field_outputs_meta: List[Dict[str, Any]] = self._metadata.setdefault("field_outputs", []) + if not any( + entry.get("path") and Path(entry.get("path")) == output_path for entry in field_outputs_meta + ): + field_outputs_meta.append({"id": field_id, "path": str(output_path)}) + + extra_downloads_meta: List[Dict[str, Any]] = self._metadata.setdefault("extra_downloads", []) + if not any( + item.get("path") and Path(item.get("path")) == output_path for item in extra_downloads_meta + ): + label = f"Field map CSV ({field_id})" if field_id else output_path.name + extra_downloads_meta.append( + { + "category": "result", + "path": str(output_path), + "label": label, + } + ) + + if not output_path.exists(): + return + + results_dir = Path(self._app.config["RESULTS_FOLDER"]) + timestamp = time.strftime("%Y%m%d-%H%M%S") + frame_suffix = f"_frame{frame}" if frame is not None else "" + filename = f"field_progress_{timestamp}{frame_suffix}.png" + image_path = results_dir / filename + + try: + render_field_map_image( + scenario_path, + output_path, + output_path=image_path, + vector_mode=DEFAULT_VECTOR_MODE, + quiver_skip=DEFAULT_QUIVER_SKIP, + color_scale=DEFAULT_COLOR_SCALE, + draw_boundaries=True, + streamlines=DEFAULT_STREAMLINES, + ) + except Exception as exc: # pragma: no cover - defensive guard + self._queue.put({ + "message": f"Failed to render field map '{field_id}': {exc}", + "warning": True, + }) + return + + caption = f"Frame {frame} field map" if frame is not None else f"Field map '{field_id}'" + + with self._lock: + processed = self._metadata.setdefault("processed_field_maps", set()) + processed.add(output_path) + self._metadata["latest_field_map"] = output_path + self._metadata["latest_image"] = image_path + sequence_index = int(self._metadata.get("frame_sequence", 0)) + self._metadata["frame_sequence"] = sequence_index + 1 + frames: List[Dict[str, Any]] = self._metadata.setdefault("rendered_frames", []) + frames.append( + { + "image": str(image_path), + "caption": caption, + "frame_index": frame, + "field_id": field_id, + "sequence_index": sequence_index, + } + ) + + self._queue.put( + { + "visualization": { + "image": f"/result-image/{image_path.name}", + "caption": caption, + "frame_index": frame, + "field_id": field_id, + "sequence_index": sequence_index, + } + } + ) + + def _candidate_field_maps(self) -> List[Path]: + scenario_path: Optional[Path] = self._metadata.get("scenario_path") + scenario_dir = scenario_path.parent if scenario_path else None + process_cwd: Optional[Path] = self._metadata.get("process_cwd") + candidates: List[Path] = [] + seen: Set[Path] = set() + latest = self._metadata.get("latest_field_map") + if isinstance(latest, Path): + candidates.append(latest) + seen.add(latest) + for entry in self._metadata.get("field_outputs", []): + path_value = entry.get("path") + if not path_value: + continue + path = _resolve_output_path( + Path(path_value), + scenario_dir=scenario_dir, + process_cwd=process_cwd, + ) + if path in seen: + continue + candidates.append(path) + seen.add(path) + return candidates + def _run_process(self, command: List[str]) -> None: log_path: Path = self._metadata["log_path"] log_path.parent.mkdir(parents=True, exist_ok=True) @@ -155,6 +363,18 @@ def _run_process(self, command: List[str]) -> None: event["progress"] = progress self._queue.put(event) + match = FIELD_MAP_REGEX.match(line) + if match: + frame_value = match.group("frame") + frame = int(frame_value) if frame_value is not None else None + field_id = match.group("id") + path_str = match.group("path") + self._handle_field_map_event( + frame=frame, + field_id=field_id, + path_str=path_str, + ) + return_code = process.wait() success = return_code == 0 and not self._stop_requested if self._stop_requested and return_code != 0: @@ -196,14 +416,107 @@ def _emit_completion(self, *, success: bool, message: str, scenario_path: Path) path = Path(extra.get("path")) if not path.exists(): continue + staged_path = _stage_result_file(path, Path(self._app.config["RESULTS_FOLDER"])) downloads.append( { "category": extra.get("category", "result"), - "filename": path.name, - "label": extra.get("label", path.name), + "filename": staged_path.name, + "label": extra.get("label", staged_path.name), } ) + visualization_payload: Optional[Dict[str, Any]] = None + if success: + for candidate in self._candidate_field_maps(): + if not candidate.exists(): + continue + scenario_path_resolved = scenario_path if scenario_path.exists() else None + results_dir = Path(self._app.config["RESULTS_FOLDER"]) + timestamp = time.strftime("%Y%m%d-%H%M%S") + image_path = results_dir / f"field_result_{timestamp}.png" + try: + render_field_map_image( + scenario_path_resolved or scenario_path, + candidate, + output_path=image_path, + vector_mode=DEFAULT_VECTOR_MODE, + quiver_skip=DEFAULT_QUIVER_SKIP, + color_scale=DEFAULT_COLOR_SCALE, + draw_boundaries=True, + streamlines=DEFAULT_STREAMLINES, + ) + except Exception as exc: # pragma: no cover - defensive guard + self._queue.put({ + "message": f"Failed to render final field map: {exc}", + "warning": True, + }) + break + + downloads.append( + { + "category": "result", + "filename": image_path.name, + "label": "Field map image", + } + ) + frame_entry = None + with self._lock: + processed = self._metadata.setdefault("processed_field_maps", set()) + processed.add(candidate) + field_outputs_meta: List[Dict[str, Any]] = self._metadata.setdefault("field_outputs", []) + field_id = None + for entry in field_outputs_meta: + path_value = entry.get("path") + if path_value and Path(path_value) == candidate: + field_id = entry.get("id") + break + sequence_index = int(self._metadata.get("frame_sequence", 0)) + self._metadata["frame_sequence"] = sequence_index + 1 + frames: List[Dict[str, Any]] = self._metadata.setdefault("rendered_frames", []) + frame_entry = { + "image": str(image_path), + "caption": "Final field map", + "frame_index": None, + "field_id": field_id, + "sequence_index": sequence_index, + } + frames.append(frame_entry) + self._metadata["latest_field_map"] = candidate + self._metadata["latest_image"] = image_path + frames_copy = list(frames) + self._last_result = { + "scenario_path": str(scenario_path), + "field_map": str(candidate), + "image": str(image_path), + "caption": "Final field map", + "settings": { + "vector_mode": DEFAULT_VECTOR_MODE, + "color_scale": DEFAULT_COLOR_SCALE, + "quiver_skip": DEFAULT_QUIVER_SKIP, + "draw_boundaries": True, + "streamlines": DEFAULT_STREAMLINES, + }, + "frames": frames_copy, + } + + visualization_payload = { + "image": f"/result-image/{image_path.name}", + "caption": "Final field map", + "frame_index": frame_entry.get("frame_index") if frame_entry else None, + "field_id": frame_entry.get("field_id") if frame_entry else None, + "sequence_index": frame_entry.get("sequence_index") if frame_entry else None, + } + break + else: + with self._lock: + frames_copy = list(self._metadata.get("rendered_frames", [])) + self._last_result = {"message": "No field map output generated.", "frames": frames_copy} + visualization_payload = {"message": "No field map output generated."} + else: + with self._lock: + frames_copy = list(self._metadata.get("rendered_frames", [])) + self._last_result = {"frames": frames_copy} + event: Dict[str, Any] = {"complete": True, "message": message, "success": success} if success: event["progress"] = 100 @@ -211,6 +524,8 @@ def _emit_completion(self, *, success: bool, message: str, scenario_path: Path) event["stopped"] = True if downloads: event["downloads"] = downloads + if visualization_payload: + event["visualization"] = visualization_payload self._queue.put(event) self._stop_requested = False @@ -244,72 +559,1216 @@ def _extract_progress(line: str) -> Optional[int]: Path(app.config["UPLOAD_FOLDER"]).mkdir(parents=True, exist_ok=True) Path(app.config["RESULTS_FOLDER"]).mkdir(parents=True, exist_ok=True) +app.secret_key = "mag-sim-gui" + manager = SimulationManager(app) +PROJECTS: Dict[str, Dict[str, Any]] = {} + def allowed_file(filename: str) -> bool: return Path(filename).suffix.lower() in ALLOWED_EXTENSIONS +def _build_form_state(overrides: Optional[Dict[str, str]] = None) -> Dict[str, str]: + state = { + "solver": DEFAULT_SOLVER, + "tol": f"{DEFAULT_TOLERANCE}", + "max_iters": f"{DEFAULT_MAX_ITERS}", + "outputs": "", + } + if overrides: + for key, value in overrides.items(): + if key in state and value is not None: + state[key] = value + return state + + +def _default_windings() -> List[Dict[str, Any]]: + windings: List[Dict[str, Any]] = [] + for phase in DEFAULT_PHASE_SEQUENCE: + windings.append( + { + "id": uuid.uuid4().hex[:8], + "name": f"Phase {phase}", + "phase": phase, + "orientation": 1.0, + "turns": DEFAULT_WINDING_TURNS, + "fill_fraction": DEFAULT_FILL_FRACTION, + "layers": [], + } + ) + return windings + + +def _register_project( + scenario_path: Path, spec: Dict[str, Any], original_name: str +) -> Tuple[str, Dict[str, Any]]: + project_id = uuid.uuid4().hex + PROJECTS[project_id] = { + "scenario_path": scenario_path, + "spec": spec, + "original_filename": original_name, + "scenario_name": spec.get("name"), + "created_label": time.strftime("%Y-%m-%d %H:%M:%S"), + "preview_path": None, + "preview_notice": None, + "preview_version": None, + "form_state": _build_form_state(), + "dxf_files": {}, + "dxf_preview_path": None, + "dxf_preview_version": None, + "dxf_notice": None, + "materials_notice": None, + "windings": _default_windings(), + "timeline_mode": "balanced", + "timeline_config": None, + "timeline_frames": 0, + } + return project_id, PROJECTS[project_id] + + +def _register_blank_project() -> Tuple[str, Dict[str, Any]]: + project_id = uuid.uuid4().hex + PROJECTS[project_id] = { + "scenario_path": None, + "spec": {}, + "original_filename": None, + "scenario_name": None, + "created_label": time.strftime("%Y-%m-%d %H:%M:%S"), + "preview_path": None, + "preview_notice": None, + "preview_version": None, + "form_state": _build_form_state(), + "dxf_files": {}, + "dxf_preview_path": None, + "dxf_preview_version": None, + "dxf_notice": None, + "materials_notice": None, + "windings": _default_windings(), + "timeline_mode": "balanced", + "timeline_config": None, + "timeline_frames": 0, + } + return project_id, PROJECTS[project_id] + + +def _ensure_project_for_dxf(project_id: Optional[str]) -> Tuple[str, Dict[str, Any]]: + active_id, project = _current_project(project_id) + if project is None: + active_id, project = _register_blank_project() + session["project_id"] = active_id + return active_id, project + + +def _update_dxf_preview(project_id: str) -> None: + project = PROJECTS.get(project_id) + if not project: + return + + preview_path = project.get("dxf_preview_path") + if isinstance(preview_path, Path): + preview_path.unlink(missing_ok=True) + + primitives = [] + dxf_store = project.get("dxf_files", {}) + if isinstance(dxf_store, dict): + for entry in dxf_store.values(): + if not isinstance(entry, dict): + continue + path = entry.get("path") + if not isinstance(path, Path) or not path.exists(): + continue + layers = entry.get("layers", []) + if not isinstance(layers, list): + continue + selected_layers = [layer.get("name") for layer in layers if layer.get("selected")] + selected_layers = [name for name in selected_layers if isinstance(name, str) and name] + if not selected_layers: + continue + categories = { + layer.get("name"): layer.get("category") or DXF_DEFAULT_CATEGORY + for layer in layers + if isinstance(layer, dict) and layer.get("name") + } + try: + primitives.extend(load_primitives(path, selected_layers, categories)) + except DxfError as exc: + project["dxf_notice"] = str(exc) + return + + if not primitives: + project["dxf_preview_path"] = None + project["dxf_preview_version"] = None + project["dxf_notice"] = "Select DXF layers to preview." + return + + output_name = f"dxf_preview_{project_id}_{time.strftime('%Y%m%d-%H%M%S')}" + preview_destination = Path(app.config["RESULTS_FOLDER"]) / f"{output_name}.png" + try: + render_preview(primitives, preview_destination) + except DxfError as exc: + project["dxf_notice"] = str(exc) + preview_destination.unlink(missing_ok=True) + return + + project["dxf_preview_path"] = preview_destination + project["dxf_preview_version"] = time.time() + project["dxf_notice"] = None + + +def _discard_project(project_id: str) -> None: + project = PROJECTS.pop(project_id, None) + if not project: + return + + preview_path = project.get("preview_path") + if isinstance(preview_path, Path): + preview_path.unlink(missing_ok=True) + + dxf_preview_path = project.get("dxf_preview_path") + if isinstance(dxf_preview_path, Path): + dxf_preview_path.unlink(missing_ok=True) + + scenario_path = project.get("scenario_path") + if isinstance(scenario_path, Path): + scenario_path.unlink(missing_ok=True) + + dxf_files = project.get("dxf_files", {}) + if isinstance(dxf_files, dict): + for entry in dxf_files.values(): + path = entry.get("path") + if isinstance(path, Path): + path.unlink(missing_ok=True) + + +def _current_project( + explicit_id: Optional[str] = None, +) -> Tuple[Optional[str], Optional[Dict[str, Any]]]: + project_id = explicit_id or session.get("project_id") + if not project_id: + return None, None + + project = PROJECTS.get(project_id) + if project is None: + if explicit_id is None: + session.pop("project_id", None) + return None, None + + return project_id, project + + +def _index_context( + *, + error: Optional[str] = None, + preview_url: Optional[str] = None, + preview_error: Optional[str] = None, + preview_notice: Optional[str] = None, + form_state: Optional[Dict[str, str]] = None, + project_id: Optional[str] = None, + status_message: Optional[str] = None, +) -> Dict[str, Any]: + active_id, project = _current_project(project_id) + + if form_state is None: + if project and project.get("form_state"): + form_state = dict(project["form_state"]) + else: + form_state = _build_form_state() + else: + form_state = _build_form_state(form_state) + + workflow_docs = { + "induction": "https://lpurdy01.github.io/mag_sim/user-guide/gui-workflows/induction-demo/", + "iron_ring": "https://lpurdy01.github.io/mag_sim/user-guide/gui-workflows/iron-ring-demo/", + } + + preview_source = preview_url + preview_token = None + notice = preview_notice + if project: + stored_preview = project.get("preview_path") + if stored_preview and Path(stored_preview).exists(): + if not preview_source: + preview_source = url_for("result_image", filename=Path(stored_preview).name) + preview_token = project.get("preview_version") + if not notice and project.get("preview_notice"): + notice = project["preview_notice"] + + last_result = manager.get_last_result() + last_image_url = None + visualization_caption = None + visualization_message = last_result.get("message") if last_result else None + visualization_defaults = { + "vector_mode": DEFAULT_VECTOR_MODE, + "color_scale": DEFAULT_COLOR_SCALE, + "quiver_skip": DEFAULT_QUIVER_SKIP, + "draw_boundaries": True, + "streamlines": DEFAULT_STREAMLINES, + } + frame_history: List[Dict[str, Any]] = [] + if last_result: + image_path = Path(last_result.get("image", "")) + if image_path.exists(): + last_image_url = url_for("result_image", filename=image_path.name) + visualization_caption = last_result.get("caption") + visualization_message = None + settings = last_result.get("settings", {}) + for key in visualization_defaults: + if key in settings: + visualization_defaults[key] = settings[key] + frames_value = last_result.get("frames") + if isinstance(frames_value, list): + for entry in frames_value: + if not isinstance(entry, dict): + continue + image_value = entry.get("image") + image_url = None + if image_value: + try: + image_path_value = Path(image_value) + except (TypeError, ValueError): # pragma: no cover - defensive guard + image_path_value = None + if image_path_value and image_path_value.exists(): + image_url = url_for("result_image", filename=image_path_value.name) + elif isinstance(image_value, str): + image_url = image_value + if not image_url: + continue + frame_history.append( + { + "image": image_url, + "caption": entry.get("caption"), + "frame_index": entry.get("frame_index"), + "field_id": entry.get("field_id"), + "sequence_index": entry.get("sequence_index"), + } + ) + frame_history.sort(key=lambda item: (item.get("sequence_index") or 0)) + + project_summary = None + project_has_scenario = False + dxf_files_summary: List[Dict[str, Any]] = [] + dxf_preview_url = None + dxf_preview_token = None + dxf_notice = None + has_dxf_selection = False + materials_palette: List[Dict[str, Any]] = [] + wire_layer_options: List[Dict[str, str]] = [] + windings_form: List[Dict[str, Any]] = [] + timeline_summary: Optional[Dict[str, Any]] = None + timeline_config = None + timeline_mode = "balanced" + + if project and active_id: + scenario_path_value = project.get("scenario_path") + scenario_exists = isinstance(scenario_path_value, Path) and scenario_path_value.exists() + project_has_scenario = scenario_exists + project_summary = { + "id": active_id, + "filename": project.get("original_filename"), + "scenario_name": project.get("scenario_name"), + "created_label": project.get("created_label"), + "has_scenario": scenario_exists, + } + + dxf_store = project.get("dxf_files", {}) + if isinstance(dxf_store, dict): + for dxf_id, entry in dxf_store.items(): + if not isinstance(entry, dict): + continue + layers_payload: List[Dict[str, Any]] = [] + for layer in entry.get("layers", []): + if not isinstance(layer, dict): + continue + category_value = layer.get("category") or DXF_DEFAULT_CATEGORY + selected_value = bool(layer.get("selected", False)) + layers_payload.append( + { + "form_id": layer.get("form_id"), + "name": layer.get("name"), + "entity_count": layer.get("entity_count", 0), + "types": layer.get("types", []), + "selected": selected_value, + "category": category_value, + } + ) + has_dxf_selection = has_dxf_selection or selected_value + layers_payload.sort(key=lambda item: (item["name"] or "").lower()) + entry_path = entry.get("path") + display_name = entry.get("original_name") or entry.get("name") + if not display_name and isinstance(entry_path, Path): + display_name = entry_path.name + dxf_files_summary.append( + { + "id": dxf_id, + "name": display_name or dxf_id, + "layers": layers_payload, + "uploaded_label": entry.get("uploaded_label"), + } + ) + dxf_files_summary.sort(key=lambda item: item["name"].lower()) + + preview_path = project.get("dxf_preview_path") + if isinstance(preview_path, Path) and preview_path.exists(): + dxf_preview_url = url_for("result_image", filename=preview_path.name) + dxf_preview_token = project.get("dxf_preview_version") + dxf_notice = project.get("dxf_notice") + + spec = project.get("spec") if isinstance(project.get("spec"), dict) else {} + if isinstance(spec, dict): + materials_palette = copy.deepcopy(spec.get("materials") or []) + if not materials_palette: + materials_palette = [ + {"name": "air", "mu_r": 1.0}, + {"name": "steel", "mu_r": 1000.0}, + ] + timeline_data = spec.get("timeline") + if isinstance(timeline_data, list) and timeline_data: + start_t = float(timeline_data[0].get("t", 0.0)) + end_t = float(timeline_data[-1].get("t", start_t)) + timeline_summary = { + "frames": len(timeline_data), + "duration": max(0.0, end_t - start_t), + } + + windings_value = project.get("windings") + if not isinstance(windings_value, list): + windings_value = _default_windings() + project["windings"] = windings_value + windings_form = copy.deepcopy(windings_value) + wire_layer_options = _wire_layer_options(project) + timeline_config = project.get("timeline_config") + timeline_mode = project.get("timeline_mode", "balanced") + if project.get("timeline_frames"): + timeline_summary = timeline_summary or {} + timeline_summary.setdefault("frames", project.get("timeline_frames")) + + return { + "running": manager.is_running, + "error": error, + "form_state": form_state, + "preview_url": preview_source, + "preview_error": preview_error, + "preview_notice": notice, + "preview_token": preview_token, + "visualization_defaults": visualization_defaults, + "last_visualization_url": last_image_url, + "last_visualization_caption": visualization_caption, + "visualization_message": visualization_message, + "visualization_visible": bool(last_result), + "last_visualization_frames": frame_history, + "visualization_has_frames": bool(frame_history), + "DEFAULT_LOG_FLOOR": DEFAULT_LOG_FLOOR, + "project": project_summary, + "has_project": project_summary is not None, + "project_download_url": url_for("project_scenario") if project_has_scenario else None, + "project_export_dxf_url": url_for("project_export_dxf") if project_has_scenario else None, + "project_has_scenario": project_has_scenario, + "form_requires_file": not project_has_scenario, + "status_message": status_message, + "dxf_files": dxf_files_summary, + "dxf_preview_url": dxf_preview_url, + "dxf_preview_token": dxf_preview_token, + "dxf_notice": dxf_notice, + "dxf_categories": DXF_CATEGORY_OPTIONS, + "has_dxf_selection": has_dxf_selection, + "materials_palette": materials_palette, + "wire_layer_options": wire_layer_options, + "windings_form": windings_form, + "timeline_summary": timeline_summary, + "timeline_config": timeline_config, + "timeline_mode": timeline_mode, + "workflow_docs": workflow_docs, + "DEFAULT_WINDING_TURNS": DEFAULT_WINDING_TURNS, + "DEFAULT_FILL_FRACTION": DEFAULT_FILL_FRACTION, + } + + +def _store_scenario_file(geometry) -> Tuple[Path, dict, str, str]: + suffix = Path(geometry.filename).suffix.lower() + timestamp = time.strftime("%Y%m%d-%H%M%S") + original_name = geometry.filename or f"scenario_{timestamp}{suffix}" + safe_name = secure_filename(original_name) or f"scenario_{timestamp}{suffix}" + scenario_path = Path(app.config["UPLOAD_FOLDER"]) / f"{timestamp}_{safe_name}" + + scenario_path.parent.mkdir(parents=True, exist_ok=True) + geometry.save(scenario_path) + try: + spec = json.loads(scenario_path.read_text(encoding="utf-8")) + except json.JSONDecodeError as exc: + scenario_path.unlink(missing_ok=True) + raise ValueError("Uploaded JSON is invalid.") from exc + return scenario_path, spec, timestamp, original_name + + +def _resolve_output_path( + raw_path: Path, *, scenario_dir: Optional[Path], process_cwd: Optional[Path] +) -> Path: + if raw_path.is_absolute(): + # The path may already be absolute if it came from a prior resolution. If + # it no longer exists we fall back to the candidate search below so that + # we can detect solver outputs written relative to a different base + # directory (e.g. the solver's working directory). + if raw_path.exists() or (scenario_dir is None and process_cwd is None): + return raw_path + + scenario_candidate: Optional[Path] = None + process_candidate: Optional[Path] = None + + if scenario_dir is not None: + scenario_candidate = (scenario_dir / raw_path).resolve() + if process_cwd is not None: + process_candidate = (process_cwd / raw_path).resolve() + + for candidate in (scenario_candidate, process_candidate): + if candidate is not None and candidate.exists(): + return candidate + + if process_candidate is not None: + return process_candidate + if scenario_candidate is not None: + return scenario_candidate + + return raw_path + + +def _collect_field_outputs(spec: dict, scenario_dir: Path) -> List[Dict[str, Any]]: + outputs: List[Dict[str, Any]] = [] + for entry in spec.get("outputs", []): + if entry.get("type") != "field_map": + continue + identifier = entry.get("id") + path_value = entry.get("path") + if not path_value and identifier: + path_value = f"outputs/{identifier}.csv" + if not path_value: + continue + raw_path = Path(path_value) + resolved = _resolve_output_path( + raw_path, scenario_dir=scenario_dir, process_cwd=Path.cwd() + ) + outputs.append({"id": identifier, "path": resolved}) + return outputs + + +def _collect_cli_field_outputs(outputs_arg: str) -> List[Dict[str, Any]]: + outputs: List[Dict[str, Any]] = [] + if not outputs_arg: + return outputs + + tokens = [token.strip() for token in re.split(r"[,\s]+", outputs_arg) if token.strip()] + for token in tokens: + if token.lower() in {"all", "none"}: + continue + identifier = token + path_override = None + if "=" in token: + identifier, path_override = token.split("=", 1) + elif ":" in token: + identifier, path_override = token.split(":", 1) + identifier = identifier.strip() + if not identifier: + continue + raw_path = Path(path_override.strip()) if path_override else Path("outputs") / f"{identifier}.csv" + outputs.append({"id": identifier, "path": raw_path}) + + return outputs + + +def _wire_layer_options(project: Dict[str, Any]) -> List[Dict[str, str]]: + options: List[Dict[str, str]] = [] + dxf_store = project.get("dxf_files", {}) + if not isinstance(dxf_store, dict): + return options + + for dxf_id, entry in dxf_store.items(): + if not isinstance(entry, dict): + continue + path = entry.get("path") + if not isinstance(path, Path) or not path.exists(): + continue + display_name = entry.get("original_name") or entry.get("name") or dxf_id + for layer in entry.get("layers", []): + if not isinstance(layer, dict): + continue + if not layer.get("selected"): + continue + category = (layer.get("category") or DXF_DEFAULT_CATEGORY).lower() + if category != "wire": + continue + layer_name = layer.get("name") + if not layer_name: + continue + options.append( + { + "value": f"{dxf_id}::{layer_name}", + "label": f"{display_name} • {layer_name}", + "file_id": dxf_id, + "layer": layer_name, + } + ) + + options.sort(key=lambda item: item["label"].lower()) + return options + + +def _resolve_layer_reference(project: Dict[str, Any], token: str) -> Tuple[Optional[Dict[str, Any]], Optional[Dict[str, Any]]]: + if "::" not in token: + return None, None + dxf_id, layer_name = token.split("::", 1) + dxf_store = project.get("dxf_files", {}) + if not isinstance(dxf_store, dict): + return None, None + entry = dxf_store.get(dxf_id) + if not isinstance(entry, dict): + return None, None + for layer in entry.get("layers", []): + if not isinstance(layer, dict): + continue + if layer.get("name") == layer_name: + return entry, layer + return entry, None + + +def _primitive_vertices(primitive: DxfPrimitive) -> List[List[float]]: + if primitive.kind == "polyline" and primitive.points: + vertices = [[float(x), float(y)] for x, y in primitive.points] + if vertices and vertices[0] != vertices[-1]: + vertices.append(vertices[0]) + return vertices + if ( + primitive.kind == "circle" + and primitive.center is not None + and primitive.radius is not None + ): + cx, cy = primitive.center + r = primitive.radius + segments = 48 + vertices: List[List[float]] = [] + for idx in range(segments): + angle = (2 * math.pi * idx) / segments + vertices.append([cx + r * math.cos(angle), cy + r * math.sin(angle)]) + vertices.append(vertices[0]) + return vertices + return [] + + +def _slugify_identifier(value: str) -> str: + token = re.sub(r"[^0-9a-zA-Z]+", "_", value).strip("_") + return token or "layer" + + +def _build_sources_from_windings(project: Dict[str, Any]) -> List[Dict[str, Any]]: + windings = project.get("windings") or [] + if not windings: + spec = project.get("spec") + if isinstance(spec, dict): + return list(spec.get("sources", [])) + return [] + + cache: Dict[Tuple[str, str], List[DxfPrimitive]] = {} + sources: List[Dict[str, Any]] = [] + for winding in windings: + if not isinstance(winding, dict): + continue + layers = winding.get("layers") or [] + if not layers: + continue + turns = float(winding.get("turns") or DEFAULT_WINDING_TURNS) + fill_fraction = float(winding.get("fill_fraction") or DEFAULT_FILL_FRACTION) + orientation = float(winding.get("orientation") or 1.0) + phase = (winding.get("phase") or winding.get("name") or "P").strip() + name = winding.get("name") or phase or "Winding" + + for token in layers: + entry, layer_info = _resolve_layer_reference(project, token) + if not entry or not layer_info: + continue + path = entry.get("path") + if not isinstance(path, Path) or not path.exists(): + continue + layer_name = layer_info.get("name") + if not layer_name: + continue + cache_key = (entry.get("id") or token, layer_name) + if cache_key not in cache: + try: + cache[cache_key] = load_primitives( + path, + [layer_name], + {layer_name: layer_info.get("category")}, + ) + except DxfError as exc: + raise exc + + primitives = cache.get(cache_key) or [] + for idx, primitive in enumerate(primitives): + vertices = _primitive_vertices(primitive) + if not vertices: + continue + slug = _slugify_identifier(layer_name) + source_id = f"{_slugify_identifier(phase)}_{slug}_{idx}" + sources.append( + { + "type": "current_region", + "id": source_id, + "label": f"{name} – {layer_name} #{idx + 1}", + "phase": phase or name, + "orientation": orientation, + "vertices": vertices, + "I": 0.0, + "turns": turns, + "fill_fraction": fill_fraction, + } + ) + + return sources + + +def _persist_project_spec(project: Dict[str, Any]) -> None: + spec = project.get("spec") + if not isinstance(spec, dict): + return + scenario_path = project.get("scenario_path") + if isinstance(scenario_path, Path): + scenario_path.parent.mkdir(parents=True, exist_ok=True) + scenario_path.write_text(json.dumps(spec, indent=2) + "\n", encoding="utf-8") + project["scenario_name"] = spec.get("name") or scenario_path.stem + + +def _stage_result_file(path: Path, results_dir: Path) -> Path: + results_dir.mkdir(parents=True, exist_ok=True) + try: + if path.exists() and path.parent.resolve() == results_dir.resolve(): + return path + except FileNotFoundError: + pass + + target = results_dir / path.name + counter = 1 + while target.exists(): + try: + if target.resolve() == path.resolve(): + return target + except FileNotFoundError: + break + target = results_dir / f"{path.stem}_{counter}{path.suffix}" + counter += 1 + + if path.exists(): + shutil.copy2(path, target) + return target + + +def _generate_balanced_timeline( + *, + amplitude: float, + frequency_hz: float, + steps_per_cycle: int, + cycles: int, + phase_offset_deg: float, + sequence: str, + dc_offset: float, +) -> Tuple[List[Dict[str, Any]], float]: + if frequency_hz <= 0: + raise ValueError("Frequency must be positive for balanced generation.") + if steps_per_cycle < MIN_TIMELINE_STEPS or cycles < 1: + raise ValueError("Increase steps/cycles to generate a usable timeline.") + + total_steps = steps_per_cycle * cycles + dt = 1.0 / frequency_hz / steps_per_cycle + frames: List[Dict[str, Any]] = [] + base_offset = math.radians(phase_offset_deg) + seq = [char.strip() for char in sequence if char.strip()] + if not seq: + seq = DEFAULT_PHASE_SEQUENCE + + for step in range(total_steps): + t = step * dt + angle = (2 * math.pi * step) / steps_per_cycle + phase_currents: Dict[str, float] = {} + for idx, phase in enumerate(seq): + phase_angle = angle + base_offset + idx * (2 * math.pi / 3) + phase_currents[phase] = amplitude * math.sin(phase_angle) + dc_offset + frames.append({"t": t, "phase_currents": phase_currents}) + + return frames, dt + + +def _parse_manual_timeline(raw: str) -> List[Dict[str, Any]]: + data = json.loads(raw) + if not isinstance(data, list): + raise ValueError("Manual timeline must be a list of frames.") + frames: List[Dict[str, Any]] = [] + for entry in data: + if not isinstance(entry, dict): + raise ValueError("Each timeline entry must be an object.") + t = entry.get("t") + if not isinstance(t, (int, float)): + raise ValueError("Timeline entries require numeric 't' values.") + currents = entry.get("phase_currents") + if not isinstance(currents, dict): + raise ValueError("Timeline entries require 'phase_currents'.") + phase_currents: Dict[str, float] = {} + for key, value in currents.items(): + if not isinstance(value, (int, float)): + raise ValueError("Phase currents must be numeric.") + phase_currents[key] = float(value) + frames.append({"t": float(t), "phase_currents": phase_currents}) + frames.sort(key=lambda item: item.get("t", 0.0)) + return frames + + +@app.post("/workspace/materials") +def update_materials() -> Response: + project_id, project = _current_project() + if project is None: + return redirect(url_for("index", error="Load a scenario to edit materials.")) + + spec = project.get("spec") + if not isinstance(spec, dict): + return redirect(url_for("index", error="Scenario metadata is unavailable.")) + + try: + count = int(request.form.get("materials-count", "0") or 0) + except ValueError: + count = 0 + + materials: List[Dict[str, Any]] = [] + for idx in range(max(0, count)): + prefix = f"material-{idx}" + name = (request.form.get(f"{prefix}-name") or "").strip() + if not name: + continue + try: + mu_r = float(request.form.get(f"{prefix}-mu", "1")) + except ValueError: + return redirect(url_for("index", error="Relative permeability must be numeric.")) + material_entry: Dict[str, Any] = {"name": name, "mu_r": mu_r} + sigma_value = request.form.get(f"{prefix}-sigma") + if sigma_value: + try: + material_entry["sigma"] = float(sigma_value) + except ValueError: + return redirect(url_for("index", error="Conductivity must be numeric.")) + materials.append(material_entry) + + if not materials: + return redirect(url_for("index", error="Add at least one material before saving.")) + + spec["materials"] = materials + project["spec"] = spec + _persist_project_spec(project) + return redirect(url_for("index", notice="Materials updated.")) + + +@app.post("/workspace/windings") +def update_windings() -> Response: + project_id, project = _current_project() + if project is None: + return redirect(url_for("index", error="Load a project to configure windings.")) + + spec = project.get("spec") + if not isinstance(spec, dict): + return redirect(url_for("index", error="Upload a scenario before assigning windings.")) + + scenario_path = project.get("scenario_path") + if not isinstance(scenario_path, Path): + return redirect(url_for("index", error="Upload a scenario before assigning windings.")) + + try: + count = int(request.form.get("winding-count", "0") or 0) + except ValueError: + count = 0 + + windings: List[Dict[str, Any]] = [] + for idx in range(max(0, count)): + prefix = f"winding-{idx}" + name = (request.form.get(f"{prefix}-name") or "").strip() + phase = (request.form.get(f"{prefix}-phase") or name or f"W{idx + 1}").strip() + layers = request.form.getlist(f"{prefix}-layers") + if not name and not layers: + continue + try: + turns = float(request.form.get(f"{prefix}-turns", DEFAULT_WINDING_TURNS)) + except ValueError: + return redirect(url_for("index", error="Turns must be numeric.")) + try: + fill_fraction = float(request.form.get(f"{prefix}-fill", DEFAULT_FILL_FRACTION)) + except ValueError: + return redirect(url_for("index", error="Fill fraction must be numeric.")) + orientation = request.form.get(f"{prefix}-orientation", "1") + orientation_value = -1.0 if orientation == "-1" else 1.0 + windings.append( + { + "id": uuid.uuid4().hex[:8], + "name": name or phase or f"Winding {idx + 1}", + "phase": phase or name or f"W{idx + 1}", + "layers": layers, + "turns": turns, + "fill_fraction": fill_fraction, + "orientation": orientation_value, + } + ) + + if not windings: + windings = _default_windings() + + project["windings"] = windings + + try: + sources = _build_sources_from_windings(project) + except DxfError as exc: + return redirect(url_for("index", error=str(exc))) + + if sources: + spec["sources"] = sources + project["spec"] = spec + _persist_project_spec(project) + + return redirect(url_for("index", notice="Windings updated.")) + + +@app.post("/workspace/timeline") +def update_timeline() -> Response: + project_id, project = _current_project() + if project is None: + return redirect(url_for("index", error="Load a scenario before editing the timeline.")) + + spec = project.get("spec") + if not isinstance(spec, dict): + return redirect(url_for("index", error="Scenario metadata is unavailable.")) + + mode = (request.form.get("timeline-mode") or "balanced").lower() + frames: List[Dict[str, Any]] = [] + dt = None + + if mode == "balanced": + try: + amplitude = float(request.form.get("timeline-amplitude", "1")) + frequency = float(request.form.get("timeline-frequency", "50")) + steps = int(request.form.get("timeline-steps", "12")) + cycles = int(request.form.get("timeline-cycles", "1")) + phase_offset = float(request.form.get("timeline-phase-offset", "0")) + dc_offset = float(request.form.get("timeline-dc-offset", "0")) + except ValueError: + return redirect(url_for("index", error="Timeline parameters must be numeric.")) + sequence = request.form.get("timeline-sequence", "ABC") + try: + frames, dt = _generate_balanced_timeline( + amplitude=amplitude, + frequency_hz=frequency, + steps_per_cycle=steps, + cycles=cycles, + phase_offset_deg=phase_offset, + sequence=sequence, + dc_offset=dc_offset, + ) + except ValueError as exc: + return redirect(url_for("index", error=str(exc))) + project["timeline_config"] = { + "amplitude": amplitude, + "frequency": frequency, + "steps": steps, + "cycles": cycles, + "phase_offset": phase_offset, + "sequence": sequence, + "dc_offset": dc_offset, + } + elif mode == "manual": + raw = request.form.get("timeline-json", "").strip() + if not raw: + return redirect(url_for("index", error="Paste timeline JSON before saving.")) + try: + frames = _parse_manual_timeline(raw) + except (ValueError, json.JSONDecodeError) as exc: + return redirect(url_for("index", error=f"Invalid timeline JSON: {exc}")) + project["timeline_config"] = {"raw": raw} + else: + return redirect(url_for("index", error="Unsupported timeline mode.")) + + if not frames: + return redirect(url_for("index", error="Timeline did not produce any frames.")) + + spec["timeline"] = frames + if dt is not None: + transient = spec.setdefault("transient", {}) + transient["dt"] = dt + transient["n_steps"] = len(frames) + project["timeline_frames"] = len(frames) + project["timeline_mode"] = mode + project["spec"] = spec + _persist_project_spec(project) + return redirect(url_for("index", notice="Timeline updated.")) + + +@app.post("/dxf/upload") +def upload_dxf_files() -> Response: + project_hint = request.form.get("project_id") + project_id, project = _ensure_project_for_dxf(project_hint) + + files = request.files.getlist("dxf_files") + if not files or all(not f.filename for f in files): + return redirect(url_for("index", error="Select at least one DXF file to import.")) + + errors: List[str] = [] + imported = 0 + dxf_store = project.setdefault("dxf_files", {}) + + for storage in files: + if not storage.filename: + continue + suffix = Path(storage.filename).suffix.lower() + if suffix != ".dxf": + errors.append(f"Unsupported extension for {storage.filename}; only DXF files are allowed.") + continue + + timestamp = time.strftime("%Y%m%d-%H%M%S") + safe_name = secure_filename(storage.filename) or f"drawing_{timestamp}.dxf" + destination = Path(app.config["UPLOAD_FOLDER"]) / f"{project_id}_{timestamp}_{safe_name}" + destination.parent.mkdir(parents=True, exist_ok=True) + storage.save(destination) + + try: + layer_summary = summarise_dxf(destination) + except DxfError as exc: + destination.unlink(missing_ok=True) + errors.append(str(exc)) + continue + + if not layer_summary: + destination.unlink(missing_ok=True) + errors.append(f"{storage.filename} does not contain supported geometry.") + continue + + entry_id = uuid.uuid4().hex + dxf_store[entry_id] = { + "id": entry_id, + "path": destination, + "original_name": storage.filename, + "uploaded_label": time.strftime("%Y-%m-%d %H:%M:%S"), + "layers": [ + { + "name": info.name, + "entity_count": info.entity_count, + "types": list(info.types), + "selected": True, + "category": DXF_DEFAULT_CATEGORY, + "form_id": uuid.uuid4().hex[:8], + } + for info in layer_summary + ], + } + imported += 1 + + notice = None + if imported: + _update_dxf_preview(project_id) + project["dxf_notice"] = None + notice = f"Imported {imported} DXF file{'s' if imported != 1 else ''}." + + if errors: + project["dxf_notice"] = errors[0] + if not imported: + return redirect(url_for("index", error=errors[0])) + if notice: + return redirect(url_for("index", notice=f"{notice} Some files could not be processed.")) + return redirect(url_for("index", notice="Some DXF files could not be processed.")) + + if notice: + return redirect(url_for("index", notice=notice)) + + return redirect(url_for("index")) + + +@app.post("/dxf//layers") +def configure_dxf_layers(dxf_id: str) -> Response: + project_hint = request.form.get("project_id") + active_id, project = _current_project(project_hint) + if not project or not active_id: + return redirect(url_for("index", error="Project not found.")) + + session["project_id"] = active_id + dxf_store = project.get("dxf_files", {}) + entry = dxf_store.get(dxf_id) if isinstance(dxf_store, dict) else None + if entry is None: + return redirect(url_for("index", error="DXF entry not found.")) + + valid_categories = {choice[0] for choice in DXF_CATEGORY_OPTIONS} + layers = entry.get("layers", []) + if isinstance(layers, list): + for layer in layers: + if not isinstance(layer, dict): + continue + form_id = layer.get("form_id") + if not form_id: + continue + selected = request.form.get(f"layer-{form_id}-selected") == "on" + category = request.form.get(f"layer-{form_id}-category") or DXF_DEFAULT_CATEGORY + if category not in valid_categories: + category = DXF_DEFAULT_CATEGORY + layer["selected"] = selected + layer["category"] = category + + project["dxf_notice"] = None + _update_dxf_preview(active_id) + + return redirect(url_for("index", notice="DXF layers updated.")) + + +@app.post("/dxf//delete") +def delete_dxf_entry(dxf_id: str) -> Response: + project_hint = request.form.get("project_id") + active_id, project = _current_project(project_hint) + if not project or not active_id: + return redirect(url_for("index", error="Project not found.")) + + session["project_id"] = active_id + dxf_store = project.get("dxf_files", {}) + entry = None + if isinstance(dxf_store, dict): + entry = dxf_store.pop(dxf_id, None) + + if isinstance(entry, dict): + path = entry.get("path") + if isinstance(path, Path): + path.unlink(missing_ok=True) + project["dxf_notice"] = None + _update_dxf_preview(active_id) + return redirect(url_for("index", notice="Removed DXF file.")) + + _update_dxf_preview(active_id) + return redirect(url_for("index", error="DXF entry not found.")) + + @app.get("/") def index() -> str: error = request.args.get("error") + notice = request.args.get("notice") return render_template( "index.html", - running=manager.is_running, - error=error, - default_solver=DEFAULT_SOLVER, - default_tolerance=DEFAULT_TOLERANCE, - default_max_iters=DEFAULT_MAX_ITERS, + **_index_context(error=error, status_message=notice), ) @app.post("/upload") def upload() -> Response: + action = (request.form.get("action") or "run").lower() if manager.is_running: return redirect(url_for("index", error="A simulation is already running.")) geometry = request.files.get("geometry_file") - if geometry is None or geometry.filename == "": - return redirect(url_for("index", error="Select a scenario JSON before running.")) + project_hint = request.form.get("project_id") + current_project_id = session.get("project_id") + + project_id: Optional[str] = None + project: Optional[Dict[str, Any]] = None + scenario_path: Optional[Path] = None + spec: Optional[Dict[str, Any]] = None - if not allowed_file(geometry.filename): - return redirect(url_for("index", error="Unsupported file type. Use JSON scenarios.")) + if geometry is not None and geometry.filename: + if not allowed_file(geometry.filename): + return redirect(url_for("index", error="Unsupported file type. Use JSON scenarios.")) - solver = (request.form.get("solver") or DEFAULT_SOLVER).lower() + suffix = Path(geometry.filename).suffix.lower() + if suffix != ".json": + return redirect(url_for("index", error="Import DXF files via the CAD workspace.")) + + if current_project_id and current_project_id in PROJECTS: + _discard_project(current_project_id) + + try: + scenario_path, spec, _, original_name = _store_scenario_file(geometry) + except ValueError as exc: + return redirect(url_for("index", error=str(exc))) + + project_id, project = _register_project(scenario_path, spec, original_name) + session["project_id"] = project_id + else: + project_id, project = _current_project(project_hint) + if project is None: + return redirect(url_for("index", error="Select or upload a scenario before continuing.")) + + scenario_path = project.get("scenario_path") + spec = project.get("spec") + if not isinstance(scenario_path, Path) or not scenario_path.exists(): + return redirect(url_for("index", error="Scenario file is missing for this project.")) + if not isinstance(spec, dict): + return redirect(url_for("index", error="Scenario metadata is unavailable for this project.")) + + assert project_id is not None and project is not None + session["project_id"] = project_id + if scenario_path is None: + scenario_path = project["scenario_path"] + if spec is None: + spec = project["spec"] + + form_state = _build_form_state( + { + "solver": request.form.get("solver"), + "tol": request.form.get("tol"), + "max_iters": request.form.get("max_iters"), + "outputs": request.form.get("outputs"), + } + ) + project["form_state"] = dict(form_state) + project["spec"] = spec + project["scenario_path"] = scenario_path + project["scenario_name"] = spec.get("name") + + if action == "preview": + preview_path_existing = project.get("preview_path") + if isinstance(preview_path_existing, Path): + preview_path_existing.unlink(missing_ok=True) + + preview_filename = f"geometry_preview_{project_id}_{time.strftime('%Y%m%d-%H%M%S')}.png" + preview_path = Path(app.config["RESULTS_FOLDER"]) / preview_filename + try: + render_geometry_preview(scenario_path, preview_path) + except RuntimeError as exc: + project["preview_path"] = None + project["preview_version"] = None + project["preview_notice"] = None + return render_template( + "index.html", + **_index_context( + preview_error=str(exc), + form_state=form_state, + project_id=project_id, + ), + ) + + project["preview_path"] = preview_path + project["preview_notice"] = ( + f"Preview generated from {project.get('original_filename') or scenario_path.name}" + ) + project["preview_version"] = time.time() + + return render_template( + "index.html", + **_index_context( + preview_notice=project["preview_notice"], + form_state=form_state, + project_id=project_id, + ), + ) + + solver = (form_state["solver"] or DEFAULT_SOLVER).lower() if solver not in {"cg", "sor"}: return redirect(url_for("index", error="Solver must be either CG or SOR.")) - tol_raw = request.form.get("tol", str(DEFAULT_TOLERANCE)) try: - tolerance = float(tol_raw) + tolerance = float(form_state["tol"]) except ValueError: return redirect(url_for("index", error="Tolerance must be numeric.")) - max_iters_raw = request.form.get("max_iters", str(DEFAULT_MAX_ITERS)) try: - max_iters = int(max_iters_raw) + max_iters = int(form_state["max_iters"]) except ValueError: return redirect(url_for("index", error="Max iterations must be an integer.")) - outputs_value = (request.form.get("outputs") or "").strip() - - suffix = Path(geometry.filename).suffix.lower() - timestamp = time.strftime("%Y%m%d-%H%M%S") - safe_name = secure_filename(geometry.filename) or f"scenario_{timestamp}{suffix}" - scenario_path = Path(app.config["UPLOAD_FOLDER"]) / f"{timestamp}_{safe_name}" - - if suffix == ".json": - scenario_path.parent.mkdir(parents=True, exist_ok=True) - geometry.save(scenario_path) - try: - json.loads(scenario_path.read_text(encoding="utf-8")) - except json.JSONDecodeError: - scenario_path.unlink(missing_ok=True) - return redirect(url_for("index", error="Uploaded JSON is invalid.")) - else: - # DXF support is not yet implemented for the Flask prototype. - return redirect(url_for("index", error="DXF uploads are not supported yet.")) + outputs_value = form_state["outputs"].strip() command = [ "./build/motor_sim", @@ -327,13 +1786,38 @@ def upload() -> Response: if outputs_value: command.extend(["--outputs", outputs_value]) - log_path = Path(app.config["RESULTS_FOLDER"]) / f"simulation_{timestamp}.log" + run_stamp = time.strftime("%Y%m%d-%H%M%S") + log_path = Path(app.config["RESULTS_FOLDER"]) / f"simulation_{run_stamp}.log" + + field_outputs = _collect_field_outputs(spec, scenario_path.parent) + existing_paths: Set[Path] = {Path(entry["path"]) for entry in field_outputs} + if outputs_value: + for entry in _collect_cli_field_outputs(outputs_value): + resolved = _resolve_output_path( + Path(entry["path"]), + scenario_dir=scenario_path.parent, + process_cwd=Path.cwd(), + ) + if resolved not in existing_paths: + field_outputs.append({"id": entry.get("id"), "path": resolved}) + existing_paths.add(resolved) + + extra_downloads = [ + { + "category": "result", + "path": str(entry["path"]), + "label": f"Field map CSV ({entry['id']})" if entry.get("id") else Path(entry["path"]).name, + } + for entry in field_outputs + ] try: manager.start( command, scenario_path=scenario_path, log_path=log_path, + extra_downloads=extra_downloads, + field_outputs=[{"id": entry.get("id"), "path": str(entry["path"])} for entry in field_outputs], ) except RuntimeError as exc: return redirect(url_for("index", error=str(exc))) @@ -390,5 +1874,137 @@ def download() -> Response: return send_file(file_path, as_attachment=True) +@app.post("/project/reset") +def project_reset() -> Response: + if manager.is_running: + return redirect(url_for("index", error="Stop the running simulation before clearing the project.")) + + project_id = session.pop("project_id", None) + if project_id: + _discard_project(project_id) + + manager.clear_last_result() + return redirect(url_for("index", notice="Project cleared.")) + + +@app.get("/project/scenario") +def project_scenario() -> Response: + project_id, project = _current_project() + if project is None: + abort(404) + + scenario_path = project.get("scenario_path") + if not isinstance(scenario_path, Path) or not scenario_path.exists(): + abort(404) + + download_name = project.get("original_filename") or scenario_path.name + return send_file(scenario_path, as_attachment=True, download_name=download_name) + + +@app.get("/project/export_dxf") +def project_export_dxf() -> Response: + project_id, project = _current_project() + if project is None: + abort(404) + + scenario_path = project.get("scenario_path") + if not isinstance(scenario_path, Path) or not scenario_path.exists(): + return redirect(url_for("index", error="Load a scenario before exporting DXF geometry.")) + + export_root = Path(app.config["RESULTS_FOLDER"]) / f"dxf_export_{uuid.uuid4().hex}" + try: + exported_paths = export_scenario_to_dxf(scenario_path, export_root) + except DxfError as exc: + shutil.rmtree(export_root, ignore_errors=True) + return redirect(url_for("index", error=str(exc))) + + if not exported_paths: + shutil.rmtree(export_root, ignore_errors=True) + return redirect(url_for("index", error="Scenario does not contain exportable geometry.")) + + archive_path = export_root / f"{scenario_path.stem}_geometry.zip" + with zipfile.ZipFile(archive_path, "w") as archive: + for item in exported_paths: + archive.write(item, arcname=item.name) + + return send_file(archive_path, as_attachment=True, download_name=archive_path.name) + + +@app.get("/result-image/") +def result_image(filename: str) -> Response: + safe_name = secure_filename(filename) + if not safe_name: + abort(400) + + file_path = Path(app.config["RESULTS_FOLDER"]) / safe_name + if not file_path.exists(): + abort(404) + + return send_file(file_path, mimetype="image/png") + + +@app.get("/visualization.png") +def visualization_image() -> Response: + last_result = manager.get_last_result() + scenario_value = last_result.get("scenario_path") + scenario_path = Path(scenario_value) if scenario_value else None + if not scenario_path or not scenario_path.exists(): + scenario_path = manager.get_scenario_path() + + field_value = last_result.get("field_map") + field_map_path = Path(field_value) if field_value else None + if not field_map_path or not field_map_path.exists(): + fallback_field = manager.get_latest_field_map() + if fallback_field and fallback_field.exists(): + field_map_path = fallback_field + + if not scenario_path or not scenario_path.exists() or not field_map_path or not field_map_path.exists(): + abort(404) + + vector_mode = request.args.get("vector", DEFAULT_VECTOR_MODE) + if vector_mode not in {"linear", "log", "off"}: + abort(400) + + try: + quiver_skip = int(request.args.get("skip", DEFAULT_QUIVER_SKIP)) + if quiver_skip < 1: + raise ValueError + except ValueError: + abort(400) + + color_scale = request.args.get("scale", DEFAULT_COLOR_SCALE) + if color_scale not in {"linear", "log"}: + abort(400) + + draw_boundaries = request.args.get("boundaries", "1") != "0" + streamlines = request.args.get("stream", "0") == "1" + + try: + log_floor_value = float(request.args.get("log_floor", DEFAULT_LOG_FLOOR)) + vector_floor_value = float(request.args.get("vector_floor", DEFAULT_LOG_FLOOR)) + except ValueError: + abort(400) + + data = render_field_map_image( + scenario_path, + field_map_path, + output_path=None, + vector_mode=vector_mode, + quiver_skip=quiver_skip, + color_scale=color_scale, + draw_boundaries=draw_boundaries, + streamlines=streamlines, + log_floor=log_floor_value, + vector_log_floor=vector_floor_value, + ) + + if isinstance(data, Path): # pragma: no cover - defensive guard + payload = data.read_bytes() + else: + payload = data + + return Response(payload, mimetype="image/png") + + if __name__ == "__main__": # pragma: no cover - manual execution helper app.run(host="0.0.0.0", port=5000, debug=True) diff --git a/python/gui/dxf_utils.py b/python/gui/dxf_utils.py new file mode 100644 index 0000000..91c2d4a --- /dev/null +++ b/python/gui/dxf_utils.py @@ -0,0 +1,267 @@ +from __future__ import annotations + +import math +from dataclasses import dataclass +from pathlib import Path +from typing import Dict, Iterable, List, Optional, Sequence, Tuple + +import ezdxf +import matplotlib + +matplotlib.use("Agg", force=True) + +import matplotlib.pyplot as plt # noqa: E402 +from matplotlib.patches import Circle # noqa: E402 + + +class DxfError(RuntimeError): + """Raised when DXF artefacts cannot be processed.""" + + +@dataclass +class DxfLayerInfo: + name: str + entity_count: int + types: Sequence[str] + + +@dataclass +class DxfPrimitive: + kind: str + points: Sequence[Tuple[float, float]] | None = None + center: Tuple[float, float] | None = None + radius: float | None = None + layer: str = "0" + category: Optional[str] = None + + +_CATEGORY_COLOURS = { + "domain": "#0d6efd", + "material": "#198754", + "magnet": "#d63384", + "wire": "#fd7e14", + "structural": "#20c997", + "unassigned": "#6c757d", +} + + +def summarise_dxf(path: Path) -> List[DxfLayerInfo]: + """Return basic layer statistics for the supplied DXF file.""" + + try: + doc = ezdxf.readfile(str(path)) + except (OSError, ezdxf.DXFStructureError, ezdxf.DXFTableEntryError) as exc: + raise DxfError(f"Failed to read DXF '{path.name}': {exc}") from exc + + counts: Dict[str, int] = {} + types: Dict[str, set[str]] = {} + + for entity in doc.modelspace(): + layer = entity.dxf.layer or "0" + counts[layer] = counts.get(layer, 0) + 1 + types.setdefault(layer, set()).add(entity.dxftype()) + + summary: List[DxfLayerInfo] = [] + for layer, count in sorted(counts.items()): + summary.append(DxfLayerInfo(name=layer, entity_count=count, types=sorted(types[layer]))) + + return summary + + +def _polyline_points(entity) -> List[Tuple[float, float]]: + if entity.dxftype() == "LWPOLYLINE": + return [(pt[0], pt[1]) for pt in entity.get_points("xy")] + if entity.dxftype() == "POLYLINE": + return [(v.dxf.location.x, v.dxf.location.y) for v in entity.vertices()] + if entity.dxftype() == "LINE": + return [ + (float(entity.dxf.start.x), float(entity.dxf.start.y)), + (float(entity.dxf.end.x), float(entity.dxf.end.y)), + ] + return [] + + +def load_primitives(path: Path, layers: Iterable[str], categories: Dict[str, Optional[str]]) -> List[DxfPrimitive]: + """Extract drawable primitives for the requested layers.""" + + selected = {layer.lower() for layer in layers} + if not selected: + return [] + + try: + doc = ezdxf.readfile(str(path)) + except (OSError, ezdxf.DXFStructureError, ezdxf.DXFTableEntryError) as exc: + raise DxfError(f"Failed to read DXF '{path.name}': {exc}") from exc + + primitives: List[DxfPrimitive] = [] + for entity in doc.modelspace(): + layer = (entity.dxf.layer or "0") + if layer.lower() not in selected: + continue + + category = categories.get(layer) + if entity.dxftype() in {"LWPOLYLINE", "POLYLINE", "LINE"}: + points = _polyline_points(entity) + if not points: + continue + primitives.append(DxfPrimitive(kind="polyline", points=points, layer=layer, category=category)) + elif entity.dxftype() == "CIRCLE": + center = (float(entity.dxf.center.x), float(entity.dxf.center.y)) + primitives.append( + DxfPrimitive( + kind="circle", + center=center, + radius=float(entity.dxf.radius), + layer=layer, + category=category, + ) + ) + + return primitives + + +def render_preview(primitives: Sequence[DxfPrimitive], output_path: Path) -> Path: + """Render DXF primitives into an image.""" + + if not primitives: + raise DxfError("No DXF layers selected for preview.") + + xmin = math.inf + xmax = -math.inf + ymin = math.inf + ymax = -math.inf + + for primitive in primitives: + if primitive.kind == "polyline" and primitive.points: + xs, ys = zip(*primitive.points) + xmin = min(xmin, min(xs)) + xmax = max(xmax, max(xs)) + ymin = min(ymin, min(ys)) + ymax = max(ymax, max(ys)) + elif primitive.kind == "circle" and primitive.center and primitive.radius is not None: + cx, cy = primitive.center + r = primitive.radius + xmin = min(xmin, cx - r) + xmax = max(xmax, cx + r) + ymin = min(ymin, cy - r) + ymax = max(ymax, cy + r) + + if not math.isfinite(xmin) or not math.isfinite(xmax) or xmin == xmax or ymin == ymax: + xmin, xmax, ymin, ymax = -0.5, 0.5, -0.5, 0.5 + + padding_x = max(0.05 * (xmax - xmin), 1e-3) + padding_y = max(0.05 * (ymax - ymin), 1e-3) + xmin -= padding_x + xmax += padding_x + ymin -= padding_y + ymax += padding_y + + fig, ax = plt.subplots(figsize=(6, 6)) + colour_cache: Dict[str, str] = {} + + for primitive in primitives: + category = primitive.category or "unassigned" + colour = colour_cache.get(category) + if colour is None: + colour = _CATEGORY_COLOURS.get(category, _CATEGORY_COLOURS["unassigned"]) + colour_cache[category] = colour + + if primitive.kind == "polyline" and primitive.points: + xs, ys = zip(*primitive.points) + ax.plot(xs, ys, color=colour, linewidth=1.2) + elif primitive.kind == "circle" and primitive.center and primitive.radius is not None: + circle = Circle(primitive.center, primitive.radius, fill=False, linewidth=1.2, color=colour) + ax.add_patch(circle) + + ax.set_aspect("equal", adjustable="box") + ax.set_xlim(xmin, xmax) + ax.set_ylim(ymin, ymax) + ax.axis("off") + + output_path.parent.mkdir(parents=True, exist_ok=True) + fig.savefig(output_path, dpi=180, bbox_inches="tight", pad_inches=0.05) + plt.close(fig) + return output_path + + +def export_scenario_to_dxf(scenario_path: Path, output_dir: Path) -> List[Path]: + """Convert a scenario JSON into a set of DXF artefacts.""" + + from python.visualize_scenario_field import compute_domain_bounds, gather_boundaries, load_scenario + + spec, wires = load_scenario(scenario_path) + output_dir.mkdir(parents=True, exist_ok=True) + + exported: List[Path] = [] + + # Domain boundary + try: + xmin, xmax, ymin, ymax = compute_domain_bounds(spec) + except ValueError as exc: + raise DxfError(str(exc)) from exc + + domain_doc = ezdxf.new("R2010") + msp = domain_doc.modelspace() + msp.add_lwpolyline( + [(xmin, ymin), (xmax, ymin), (xmax, ymax), (xmin, ymax), (xmin, ymin)], + dxfattribs={"layer": "domain"}, + ) + domain_path = output_dir / "domain.dxf" + domain_doc.saveas(domain_path) + exported.append(domain_path) + + # Material regions + boundaries = gather_boundaries(spec) + region_doc = ezdxf.new("R2010") + region_msp = region_doc.modelspace() + for index, (xs, ys, material) in enumerate(boundaries.get("polygons", [])): + if len(xs) < 2: + continue + coords = list(zip(xs, ys)) + if coords[0] != coords[-1]: + coords.append(coords[0]) + layer_name = f"material_{material or index}" + region_msp.add_lwpolyline(coords, dxfattribs={"layer": layer_name}) + if len(region_msp): + regions_path = output_dir / "materials.dxf" + region_doc.saveas(regions_path) + exported.append(regions_path) + + magnet_doc = ezdxf.new("R2010") + magnet_msp = magnet_doc.modelspace() + for idx, (xs, ys) in enumerate(boundaries.get("magnet_polygons", [])): + if len(xs) < 2: + continue + coords = list(zip(xs, ys)) + if coords[0] != coords[-1]: + coords.append(coords[0]) + magnet_msp.add_lwpolyline(coords, dxfattribs={"layer": f"magnet_{idx}"}) + for idx, rect in enumerate(boundaries.get("magnet_rects", [])): + xmin_r, xmax_r, ymin_r, ymax_r = rect + coords = [ + (xmin_r, ymin_r), + (xmax_r, ymin_r), + (xmax_r, ymax_r), + (xmin_r, ymax_r), + (xmin_r, ymin_r), + ] + magnet_msp.add_lwpolyline(coords, dxfattribs={"layer": f"magnet_rect_{idx}"}) + if len(magnet_msp): + magnets_path = output_dir / "magnets.dxf" + magnet_doc.saveas(magnets_path) + exported.append(magnets_path) + + if wires: + wire_doc = ezdxf.new("R2010") + wire_msp = wire_doc.modelspace() + for idx, wire in enumerate(wires): + wire_msp.add_circle( + center=(wire.x, wire.y), + radius=wire.radius, + dxfattribs={"layer": f"wire_{idx}"}, + ) + wires_path = output_dir / "wires.dxf" + wire_doc.saveas(wires_path) + exported.append(wires_path) + + return exported diff --git a/python/gui/render.py b/python/gui/render.py new file mode 100644 index 0000000..71e8474 --- /dev/null +++ b/python/gui/render.py @@ -0,0 +1,142 @@ +"""Helpers for rendering geometry previews and field visualisations.""" + +from __future__ import annotations + +import io +from pathlib import Path +from typing import Dict, List, Optional + +import matplotlib + +matplotlib.use("Agg", force=True) + +import matplotlib.pyplot as plt # noqa: E402 - backend configured above +from matplotlib.patches import Rectangle # noqa: E402 + +from python.visualize_scenario_field import ( # noqa: E402 + Wire, + compute_domain_bounds, + gather_boundaries, + load_field_csv, + load_scenario, + plot_field, + reshape_field, +) + + +DEFAULT_LOG_FLOOR = 1e-7 + + +def render_geometry_preview(scenario_path: Path, output_path: Path) -> Path: + """Render a static geometry preview for the supplied scenario.""" + + spec, wires = load_scenario(scenario_path) + boundaries = {} + try: + boundaries = gather_boundaries(spec) + xmin, xmax, ymin, ymax = compute_domain_bounds(spec) + except ValueError as exc: + raise RuntimeError(str(exc)) from exc + + fig, ax = plt.subplots(figsize=(6.0, 5.0)) + + width = xmax - xmin + height = ymax - ymin + rect = Rectangle((xmin, ymin), width, height, fill=False, linewidth=1.6) + ax.add_patch(rect) + + for xs, ys, _material in boundaries.get("polygons", []): + if len(xs) < 2: + continue + closed_xs = xs + [xs[0]] + closed_ys = ys + [ys[0]] + ax.plot(closed_xs, closed_ys, color="tab:blue", linestyle="-", linewidth=1.0, alpha=0.7) + + for xs, ys in boundaries.get("magnet_polygons", []): + if len(xs) < 2: + continue + closed_xs = xs + [xs[0]] + closed_ys = ys + [ys[0]] + ax.plot(closed_xs, closed_ys, color="tab:red", linestyle=":", linewidth=1.2, alpha=0.9) + + for xmin_m, xmax_m, ymin_m, ymax_m in boundaries.get("magnet_rects", []): + rect_x = [xmin_m, xmax_m, xmax_m, xmin_m, xmin_m] + rect_y = [ymin_m, ymin_m, ymax_m, ymax_m, ymin_m] + ax.plot(rect_x, rect_y, color="tab:red", linestyle=":", linewidth=1.1, alpha=0.9) + + for wire in wires: + color = "tab:red" if wire.current >= 0 else "tab:blue" + circle = plt.Circle((wire.x, wire.y), wire.radius, color=color, fill=False, linewidth=1.3) + ax.add_patch(circle) + + ax.set_aspect("equal", adjustable="box") + ax.set_xlabel("x [m]") + ax.set_ylabel("y [m]") + ax.set_title(spec.get("name", scenario_path.name)) + ax.set_xlim(xmin, xmax) + ax.set_ylim(ymin, ymax) + ax.grid(False) + + output_path.parent.mkdir(parents=True, exist_ok=True) + fig.savefig(output_path, dpi=180, bbox_inches="tight") + plt.close(fig) + return output_path + + +def render_field_map_image( + scenario_path: Path, + field_map_path: Path, + *, + output_path: Optional[Path] = None, + vector_mode: str = "linear", + quiver_skip: int = 4, + color_scale: str = "linear", + draw_boundaries: bool = True, + streamlines: bool = False, + log_floor: float = DEFAULT_LOG_FLOOR, + vector_log_floor: float = DEFAULT_LOG_FLOOR, +) -> bytes | Path: + """Render the field map with the supplied styling options.""" + + spec, wires = load_scenario(scenario_path) + xs, ys, bxs, bys, bmags = load_field_csv(field_map_path) + grid_x, grid_y, (bx, by, bmag) = reshape_field(xs, ys, (bxs, bys, bmags)) + + boundaries: Dict[str, List] = {} + if draw_boundaries: + try: + boundaries = gather_boundaries(spec) + except ValueError: + boundaries = {} + + buffer = io.BytesIO() + plot_field( + grid_x, + grid_y, + bx, + by, + bmag, + wires, + quiver_skip, + buffer, + f"Field map: {scenario_path.name}", + color_scale, + log_floor, + draw_boundaries, + boundaries, + None, + vector_mode, + vector_log_floor, + streamlines, + None, + 0, + show=False, + ) + + data = buffer.getvalue() + if output_path is not None: + output_path.parent.mkdir(parents=True, exist_ok=True) + output_path.write_bytes(data) + return output_path + + return data diff --git a/python/gui/templates/base.html b/python/gui/templates/base.html index 48172a8..0674c74 100644 --- a/python/gui/templates/base.html +++ b/python/gui/templates/base.html @@ -3,24 +3,162 @@ - mag_sim Flask GUI + mag_sim Studio + {% block head %}{% endblock %} - -