123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676(* Claude Code
*
* Copyright (C) 2026 Yoann Padioleau
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public License
* (LGPL) as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*)(* See Cad_session.mli *)moduleG=Cad_geommoduleD=Cad_drawingtypept=G.pttypeview={center:pt;upp:float;w:float;h:float}typeinput=Textofstring|Pickofpt|Canceltypewant=Idle|Point|Objects|Othertypeio=Saveofstring*string|Loadofstring(*****************************************************************************)(* The states *)(*****************************************************************************)(* what a selection of objects is gathered for *)typeafter=For_erase|For_moveofbool(* a copy? *)|For_blockofstring*pt|For_edgesofbool(* EXTEND? *)(* each command's steps: where it is, and what it has been told *)typestate=|Line_from|Line_toof{first:pt;last:pt;segs:(int*pt)list(* the lines drawn, newest first, and their starts *)}|Circle_center|Circle_radiusofpt*bool(* the center; a diameter asked? *)|Arc_pointsofptlist|Selectof{after:after;picked:intlist;corner:ptoption}|Baseofbool*intlist|Secondofbool*intlist*pt|Offset_distance|Offset_pick|Offset_sideofint|Trim_pickofbool*intlist(* EXTEND?, the edges *)|Fillet_first|Fillet_radius|Fillet_secondofint*pt|Dim_pointsofptlist|Block_name|Block_baseofstring|Insert_name|Insert_pointofstring|Insert_scaleofstring*pt|Insert_rotationofstring*pt*float|Layer_option|Layer_nameofstring(* the option: M, S, ON, OFF *)|Layer_color|Layer_color_nameofint|Zoom_option|Zoom_cornerofptoption|Pan_pointsofptoption|Id_point|Dist_pointsofptoption|File_nameofbool(* writing? *)(* the drawing before the command: what U gives back *)typerun={name:string;state:state;before:D.t}typet={drawing:D.t;view:view;previous_view:viewoption;run:runoption;last_command:stringoption;last_point:ptoption;undo:(string*D.t)list;redo:(string*D.t)list;log:stringlist;fillet_radius:float;offset_distance:float;file:string;io:iooption;}letstartdrawingview={drawing;view;previous_view=None;run=None;last_command=None;last_point=None;undo=[];redo=[];log=[];fillet_radius=0.;offset_distance=1.;file="drawing";io=None;}letdrawingt=t.drawingletviewt=t.viewletlogt=t.logletiot=t.ioletrunningt=Option.map(funr->r.name)t.runletresizewht={twithview={t.viewwithw;h}}(* AutoCAD's four decimals *)letfmtx=Printf.sprintf"%.4f"xletsaylinet=letlog=t.log@[line]inletn=List.lengthlogin{twithlog=(ifn>200thenList.filteri(funi_->i>=n-200)logelselog)}(* the pick box: a click this near an entity picks it *)letpickboxt=6.*.t.view.upp(*****************************************************************************)(* Prompts *)(*****************************************************************************)letpromptt=matcht.runwith|None->"Command:"|Somer->(matchr.statewith|Line_from->"From point:"|Line_to_->"To point:"|Circle_center->"<Center point>:"|Circle_radius(_,false)->"Diameter/<Radius>:"|Circle_radius(_,true)->"Diameter:"|Arc_points[]->"<Start point>:"|Arc_points[_]->"<Second point>:"|Arc_points_->"End point:"|Select{corner=None;_}->"Select objects:"|Select{corner=Some_;_}->"Other corner:"|Base_->"Base point or displacement:"|Second_->"Second point of displacement:"|Offset_distance->Printf.sprintf"Offset distance <%s>:"(fmtt.offset_distance)|Offset_pick->"Select object to offset:"|Offset_side_->"Side to offset?"|Trim_pick(false,_)->"<Select object to trim>:"|Trim_pick(true,_)->"<Select object to extend>:"|Fillet_first->"Radius/<Select first object>:"|Fillet_radius->Printf.sprintf"Enter fillet radius <%s>:"(fmtt.fillet_radius)|Fillet_second_->"Select second object:"|Dim_points[]->"First extension line origin:"|Dim_points[_]->"Second extension line origin:"|Dim_points_->"Dimension line location:"|Block_name|Insert_name->"Block name (or ?):"|Block_base_->"Insertion base point:"|Insert_point_->"Insertion point:"|Insert_scale_->"X scale factor <1>:"|Insert_rotation_->"Rotation angle <0>:"|Layer_option->"?/Make/Set/ON/OFF/Color:"|Layer_name("M"|"S")->Printf.sprintf"New current layer <%s>:"t.drawing.current|Layer_name"ON"->"Layer name(s) to turn On:"|Layer_name_->"Layer name(s) to turn Off:"|Layer_color->"Color:"|Layer_color_namec->Printf.sprintf"Layer name(s) for color %d <%s>:"ct.drawing.current|Zoom_option->"All/Extents/Previous/Window/<Scale(X)>:"|Zoom_cornerNone->"First corner:"|Zoom_corner(Some_)->"Other corner:"|Pan_pointsNone->"Displacement:"|Pan_points(Some_)->"Second point:"|Id_point->"Point:"|Dist_pointsNone->"First point:"|Dist_points(Some_)->"Second point:"|File_name_->Printf.sprintf"File name <%s>:"t.file)letwantst=matcht.runwith|None->Idle|Somer->(matchr.statewith|Select_|Offset_pick|Trim_pick_|Fillet_first|Fillet_second_->Objects|Block_name|Insert_name|Layer_option|Layer_name_|Layer_color|Layer_color_name_|File_name_|Offset_distance|Fillet_radius|Insert_scale_|Zoom_option->Other|_->Point)(*****************************************************************************)(* Points typed *)(*****************************************************************************)(* "x,y", "@dx,dy", "@d<a", "d<a"; a distance alone, towards the cursor
from the last point *)letparse_pointt?cursors=lets=String.trimsinletrel=String.lengths>0&&s.[0]='@'inlets=ifrelthenString.subs1(String.lengths-1)elsesinletbase=ifrelthenOption.valuet.last_point~default:(0.,0.)else(0.,0.)inletnumx=float_of_string_opt(String.trimx)inmatchString.index_opts'<'with|Somei->(match(num(String.subs0i),num(String.subs(i+1)(String.lengths-i-1)))with|Somed,Somea->Some(G.polarbaseda)|_->None)|None->(matchString.split_on_char','swith|[x;y]|[x;y;_]->(match(numx,numy)withSomex,Somey->Some(G.addbase(x,y))|_->None)|[d]whennotrel->(match(numd,t.last_point,cursor)with|Somed,Somelast,SomecwhenG.distlastc>0.->Some(G.polarlastd(G.anglelastc))|_->None)|_->None)letpoint_oft?cursor=functionPickp->Somep|Texts->parse_pointt?cursors|Cancel->None(* a number typed; or two points (the last and one picked): their
distance *)letnumber_oft=function|Texts->float_of_string_opt(String.trims)|Pickp->Option.map(funl->G.distlp)t.last_point|Cancel->None(*****************************************************************************)(* The view *)(*****************************************************************************)letfitt(ax,ay)(bx,by)=letw=Float.abs(bx-.ax)andh=Float.abs(by-.ay)inletupp=Float.max(w/.t.view.w)(h/.t.view.h)*.1.1inletupp=ifupp>0.thenuppelset.view.uppin{twithprevious_view=Somet.view;view={t.viewwithcenter=((ax+.bx)/.2.,(ay+.by)/.2.);upp}}letzoom_extentst=matchD.extentst.drawingwithSome(a,b)->fittab|None->t(*****************************************************************************)(* Editing helpers *)(*****************************************************************************)(* an entity added on a given layer *)letadd_onlayere(d:D.t)=letcurrent=d.currentinletd,id=D.adde{dwithcurrent=layer}in({dwithcurrent},id)letentitytid=Option.map(fun(e:D.ent)->e.entity)(D.gett.drawingid)letlayer_oftid=matchD.gett.drawingidwithSomee->e.layer|None->t.drawing.currentletcircumcenter(ax,ay)(bx,by)(cx,cy)=letd=2.*.((ax*.(by-.cy))+.(bx*.(cy-.ay))+.(cx*.(ay-.by)))inifFloat.absd<1e-12thenNoneelseleta2=(ax*.ax)+.(ay*.ay)andb2=(bx*.bx)+.(by*.by)andc2=(cx*.cx)+.(cy*.cy)inSome(((a2*.(by-.cy))+.(b2*.(cy-.ay))+.(c2*.(ay-.by)))/.d,((a2*.(cx-.bx))+.(b2*.(ax-.cx))+.(c2*.(bx-.ax)))/.d)(* the arc from a through b to c *)letarc3abc=matchcircumcenterabcwith|None->None|Someo->lets=G.angleoaande=G.angleocinifG.withinse(G.angleob)thenSome(D.Arc(o,G.distoa,s,e))elseSome(D.Arc(o,G.distoa,e,s))letcolor_ofs=matchString.uppercase_ascii(String.trims)with|"RED"->Some1|"YELLOW"->Some2|"GREEN"->Some3|"CYAN"->Some4|"BLUE"->Some5|"MAGENTA"->Some6|"WHITE"->Some7|s->(matchint_of_string_optswithSomecwhenc>=1&&c<=255->Somec|_->None)(*****************************************************************************)(* Starting and ending commands *)(*****************************************************************************)letaliases=[("L","LINE");("C","CIRCLE");("A","ARC");("E","ERASE");("M","MOVE");("CO","COPY");("CP","COPY");("O","OFFSET");("TR","TRIM");("EX","EXTEND");("F","FILLET");("DLI","DIMLINEAR");("B","BLOCK");("I","INSERT");("LA","LAYER");("Z","ZOOM");("P","PAN");("DI","DIST");("UNDO","U");("SAVE","DXFOUT");("OPEN","DXFIN");]letsettstate=matcht.runwithSomer->{twithrun=Some{rwithstate}}|None->t(* the command over: an undo step if it changed the drawing *)letfinisht=matcht.runwith|None->t|Somer->lett={twithrun=None}in(* by value, but for the ids' counter: a line drawn then taken
back by U changed nothing *)if{t.drawingwithnext=0}<>{r.beforewithnext=0}then{twithundo=(r.name,r.before)::t.undo;redo=[]}elsetletbegin_namet=letname=String.uppercase_ascii(String.trimname)inletname=Option.value(List.assoc_optnamealiases)~default:nameinletrunstate={twithrun=Some{name;state;before=t.drawing};last_command=Somename}inmatchnamewith|"LINE"->runLine_from|"CIRCLE"->runCircle_center|"ARC"->run(Arc_points[])|"ERASE"->run(Select{after=For_erase;picked=[];corner=None})|"MOVE"->run(Select{after=For_movefalse;picked=[];corner=None})|"COPY"->run(Select{after=For_movetrue;picked=[];corner=None})|"OFFSET"->runOffset_distance|"TRIM"->say"Select cutting edges:"(run(Select{after=For_edgesfalse;picked=[];corner=None}))|"EXTEND"->say"Select boundary edges:"(run(Select{after=For_edgestrue;picked=[];corner=None}))|"FILLET"->runFillet_first|"DIMLINEAR"->run(Dim_points[])|"BLOCK"->runBlock_name|"INSERT"->runInsert_name|"LAYER"->runLayer_option|"ZOOM"->runZoom_option|"PAN"->run(Pan_pointsNone)|"ID"->runId_point|"DIST"->run(Dist_pointsNone)|"DXFOUT"->run(File_nametrue)|"DXFIN"->run(File_namefalse)|"U"->(matcht.undowith|(n,d)::rest->sayn{twithdrawing=d;undo=rest;redo=(n,t.drawing)::t.redo;last_command=Some"U"}|[]->say"Everything has been undone"t)|"REDO"->(matcht.redowith|(n,d)::rest->sayn{twithdrawing=d;redo=rest;undo=(n,t.drawing)::t.undo}|[]->say"Previous command did not undo things"t)|_->say(Printf.sprintf"Unknown command \"%s\". Type ? for list of commands."name)tletcommands="LINE CIRCLE ARC ERASE MOVE COPY OFFSET TRIM EXTEND FILLET DIMLINEAR BLOCK INSERT LAYER ZOOM PAN ID DIST U REDO DXFOUT DXFIN"letcommandnamet=lett=ift.run<>Nonethenfinish(say"*Cancel*"t)elsetinbegin_name(say("Command: "^String.uppercase_asciiname)t)(*****************************************************************************)(* The steps *)(*****************************************************************************)letselection_steptafterpickedcorneri=letselectpicked=sett(Select{after;picked;corner=None})inletfoundids=letfresh=List.filter(funid->not(List.memidpicked))idsinsay(Printf.sprintf"%d found"(List.lengthids))(select(picked@fresh))inmatch(i,corner)with|Pickp,None->(matchD.pickt.drawing(pickboxt)pwith|Someid->found[id]|None->sett(Select{after;picked;corner=Somep}))|Pickq,Somep->found(D.windowt.drawingpq~crossing:(fstq<fstp))|Texts,NonewhenString.uppercase_ascii(String.trims)="ALL"->found(D.idst.drawing)|Texts,NonewhenString.uppercase_ascii(String.trims)="L"->(matchList.rev(D.idst.drawing)withid::_->found[id]|[]->t)|Text"",_->((* Enter: the selection is made *)matchafterwith|For_erase->finish{twithdrawing=D.removepickedt.drawing}|For_movecopy->ifpicked=[]thenfinishtelsesett(Base(copy,picked))|For_block(name,base)->ifpicked=[]thenfinishtelseletes=List.filter_map(D.gett.drawing)pickedinletblocks=List.remove_assocnamet.drawing.blocks@[(name,(base,es))]infinish{twithdrawing={(D.removepickedt.drawing)withblocks}}|For_edgesextend->sett(Trim_pick(extend,picked)))|Text_,_->say"*Invalid selection*"t|Cancel,_->tletmove_objectstcopyobjsd=List.fold_left(fun(dr:D.t)id->matchD.getdridwith|Somee->letmoved=D.move_entityde.entityinifcopythenfst(add_one.layermoveddr)elseD.replaceid[moved]dr|None->dr)t.drawingobjsletstep?cursortri=letpoint()=point_oft?cursoriinletwith_pointf=matchpoint()withSomep->fp|None->say"Invalid point."tinletrememberpt={twithlast_point=Somep}inmatchr.statewith|Line_from->with_point(funp->rememberp(sett(Line_to{first=p;last=p;segs=[]})))|Line_to{first;last;segs}->(matchiwith|Text""->finisht|TextswhenString.uppercase_ascii(String.trims)="C"->ifList.lengthsegs<2thensay"Invalid point."telsefinish(rememberfirst{twithdrawing=fst(D.add(D.Line(last,first))t.drawing)})|TextswhenString.uppercase_ascii(String.trims)="U"->(matchsegswith|(id,from)::rest->rememberfrom(set{twithdrawing=D.remove[id]t.drawing}(Line_to{first;last=from;segs=rest}))|[]->say"All segments already undone."t)|_->with_point(funp->letdrawing,id=D.add(D.Line(last,p))t.drawinginrememberp(set{twithdrawing}(Line_to{first;last=p;segs=(id,last)::segs}))))|Circle_center->with_point(func->rememberc(sett(Circle_radius(c,false))))|Circle_radius(c,diameter)->(matchiwith|TextswhenString.uppercase_ascii(String.trims)="D"->sett(Circle_radius(c,true))|_->(match(matchiwithPickp->Some(G.distcp)|_->number_ofti)with|Somevwhenv>0.->finish{twithdrawing=fst(D.add(D.Circle(c,ifdiameterthenv/.2.elsev))t.drawing)}|_->say"Requires numeric distance or second point."t))|Arc_pointsps->with_point(funp->matchps@[p]with|[a;b;c]->(matcharc3abcwith|Somearc->finish(rememberp{twithdrawing=fst(D.addarct.drawing)})|None->say"Points are collinear."t)|ps->rememberp(sett(Arc_pointsps)))|Select{after;picked;corner}->selection_steptafterpickedcorneri|Base(copy,objs)->with_point(funp->rememberp(sett(Second(copy,objs,p))))|Second(copy,objs,base)->(matchiwith|Text""->finish{twithdrawing=move_objectstcopyobjsbase}(* the base as the displacement *)|_->with_point(funp->finish(rememberp{twithdrawing=move_objectstcopyobjs(G.subpbase)})))|Offset_distance->(matchiwith|Text""->settOffset_pick|_->(matchnumber_oftiwith|Somedwhend>0.->set{twithoffset_distance=d}Offset_pick|_->say"Requires a positive distance."t))|Offset_pick->(matchiwith|Text""->finisht|Pickp->(matchD.pickt.drawing(pickboxt)pwithSomeid->sett(Offset_sideid)|None->say"No object found."t)|_->t)|Offset_sideid->with_point(funside->matchentitytidwith|Somee->(matchCad_edit.offsett.offset_distancee~sidewith|Oke'->set{twithdrawing=fst(add_on(layer_oftid)e't.drawing)}Offset_pick|Errormsg->saymsg(settOffset_pick))|None->settOffset_pick)|Trim_pick(extend,edges)->(matchiwith|Text""->finisht|Pickp->(matchD.pickt.drawing(pickboxt)pwith|None->say"No object found."t|Someid->((* no edges chosen: every other entity is one *)letedges=List.filter((<>)id)(ifedges=[]thenD.idst.drawingelseedges)inletcurves=List.concat_map(fune->List.mapfst(D.piecest.drawinge))(List.filter_map(D.gett.drawing)edges)inmatchentitytidwith|None->t|Somee->(letresult=ifextendthenResult.map(fune->[e])(Cad_edit.extendcurvese~at:p)elseCad_edit.trimcurvese~at:pinmatchresultwith|Okes->{twithdrawing=D.replaceidest.drawing}|Errormsg->saymsgt)))|_->t)|Fillet_first->(matchiwith|TextswhenString.uppercase_ascii(String.trims)="R"->settFillet_radius|Text""->finisht|Pickp->(matchD.pickt.drawing(pickboxt)pwith|Someidwhen(matchentitytidwithSome(D.Line_)->true|_->false)->sett(Fillet_second(id,p))|Some_->say"Can only fillet lines."t|None->say"No object found."t)|_->t)|Fillet_radius->(matchiwith|Text""->finisht|_->(matchnumber_oftiwithSomerwhenr>=0.->finish{twithfillet_radius=r}|_->say"Requires a distance."t))|Fillet_second(id1,q1)->(matchiwith|Pickq2->(matchD.pickt.drawing(pickboxt)q2with|Someid2whenid2<>id1->(match(entitytid1,entitytid2)with|Somee1,Somee2->(matchCad_edit.fillett.fillet_radius(e1,q1)(e2,q2)with|Ok(l1,l2,arc)->letd=D.replaceid2[l2](D.replaceid1[l1]t.drawing)inletd=matcharcwithSomea->fst(add_on(layer_oftid1)ad)|None->dinfinish{twithdrawing=d}|Errormsg->finish(saymsgt))|_->finisht)|_->say"No object found."t)|Text""->finisht|_->t)|Dim_pointsps->with_point(funp->matchps@[p]with|[a;b;l]->finish{twithdrawing=fst(D.add(D.Dimension(a,b,l))t.drawing)}|ps->rememberp(sett(Dim_pointsps)))|Block_name|Insert_name->(matchiwith|Text""->finisht|Text"?"->say("Defined blocks: "^String.concat" "(List.mapfstt.drawing.blocks))t|Texts->letname=String.uppercase_ascii(String.trims)inifr.state=Block_namethensett(Block_basename)elseifList.mem_assocnamet.drawing.blocksthensett(Insert_pointname)elsefinish(say(Printf.sprintf"Block %s not found."name)t)|_->t)|Block_basename->with_point(funp->sett(Select{after=For_block(name,p);picked=[];corner=None}))|Insert_pointname->with_point(funp->rememberp(sett(Insert_scale(name,p))))|Insert_scale(name,p)->(matchiwith|Text""->sett(Insert_rotation(name,p,1.))|_->(matchnumber_oftiwithSomeswhens<>0.->sett(Insert_rotation(name,p,s))|_->say"Requires a number."t))|Insert_rotation(name,p,s)->(letplacerot=finish{twithdrawing=fst(D.add(D.Insert(name,p,s,rot))t.drawing)}inmatchiwith|Text""->place0.|Pickq->place(G.anglepq)|Textx->(matchfloat_of_string_opt(String.trimx)withSomea->placea|None->say"Requires an angle."t)|Cancel->t)|Layer_option->(matchiwith|Text""->finisht|Texts->(matchString.uppercase_ascii(String.trims)with|"?"->List.fold_left(funt(l:D.layer)->say(Printf.sprintf"%-12s %-4s %d%s"l.name(ifl.onthen"On"else"Off")l.color(ifl.name=t.drawing.currentthen" (current)"else""))t)tt.drawing.layers|"M"|"MAKE"->sett(Layer_name"M")|"S"|"SET"->sett(Layer_name"S")|"ON"->sett(Layer_name"ON")|"OFF"->sett(Layer_name"OFF")|"C"|"COLOR"->settLayer_color|_->say"Invalid option keyword."t)|_->t)|Layer_nameoption->(matchiwith|Texts->letname=String.uppercase_ascii(String.trims)inletd=t.drawinginlett=ifname=""thentelsematchoptionwith|"M"->{twithdrawing={(D.ensure_layernamed)withcurrent=name}}|"S"->ifList.exists(fun(l:D.layer)->l.name=name)d.layersthen{twithdrawing={dwithcurrent=name}}elsesay(Printf.sprintf"Cannot find layer %s."name)t|_->{twithdrawing=D.set_layer{(D.layerdname)withon=option="ON"}d}insettLayer_option|_->t)|Layer_color->(matchiwith|Texts->(matchcolor_ofswithSomec->sett(Layer_color_namec)|None->say"Invalid color."t)|_->t)|Layer_color_namec->(matchiwith|Texts->letname=ifString.trims=""thent.drawing.currentelseString.uppercase_ascii(String.trims)inset{twithdrawing=D.set_layer{(D.layert.drawingname)withcolor=c}t.drawing}Layer_option|_->t)|Zoom_option->(matchiwith|Pickp->sett(Zoom_corner(Somep))|Texts->(matchString.uppercase_ascii(String.trims)with|"A"|"ALL"|"E"|"EXTENTS"->finish(zoom_extentst)|"W"|"WINDOW"->sett(Zoom_cornerNone)|"P"|"PREVIOUS"->finish(matcht.previous_viewwithSomev->{twithview=v;previous_view=Somet.view}|None->t)|s->(lets=ifString.ends_with~suffix:"X"sthenString.subs0(String.lengths-1)elsesinmatchfloat_of_string_optswith|Somekwhenk>0.->finish{twithprevious_view=Somet.view;view={t.viewwithupp=t.view.upp/.k}}|_->say"Invalid option keyword."t))|Cancel->t)|Zoom_cornerNone->with_point(funp->sett(Zoom_corner(Somep)))|Zoom_corner(Somea)->with_point(funb->finish(fittab))|Pan_pointsNone->with_point(funp->sett(Pan_points(Somep)))|Pan_points(Somea)->with_point(funb->finish{twithprevious_view=Somet.view;view={t.viewwithcenter=G.subt.view.center(G.subba)}})|Id_point->with_point(fun(x,y)->finish(remember(x,y)(say(Printf.sprintf"X = %s Y = %s Z = %s"(fmtx)(fmty)(fmt0.))t)))|Dist_pointsNone->with_point(funp->rememberp(sett(Dist_points(Somep))))|Dist_points(Somea)->with_point(funb->letdx,dy=G.subbainfinish(say(Printf.sprintf"Distance = %s, Angle in XY Plane = %s, Delta X = %s, Delta Y = %s"(fmt(G.distab))(Printf.sprintf"%.0f"(G.angleab))(fmtdx)(fmtdy))t))|File_namewriting->(matchiwith|Texts->letbase=ifString.trims=""thent.fileelseString.trimsinletbase=ifFilename.check_suffixbase".dxf"thenFilename.chop_suffixbase".dxf"elsebaseinletname=base^".dxf"inlett={twithfile=base}inifwritingthenfinish(say("Written "^name){twithio=Some(Save(name,Dxf.to_stringt.drawing))})elsefinish{twithio=Some(Loadname)}|_->t)letinput?cursorti=lett={twithio=None}inmatch(t.run,i)with|None,Text""->(matcht.last_commandwithSomec->begin_c(say("Command: "^c)t)|None->say"Command:"t)|None,Text"?"->saycommands(say"Command: ?"t)|None,Texts->begin_s(say("Command: "^String.uppercase_ascii(String.trims))t)|None,(Pick_|Cancel)->t|Some_,Cancel->finish(say"*Cancel*"t)|Somer,_->letecho=matchiwithTexts->" "^s|_->""instep?cursor(say(promptt^echo)t)riletloadednametextt=matchtextwith|None->say(Printf.sprintf"Can't open file %s"name)t|Somes->(matchDxf.of_stringswith|Okd->say("Read "^name)(zoom_extents{twithdrawing=d;undo=("DXFIN",t.drawing)::t.undo;redo=[]})|Errore->say(Printf.sprintf"Error in %s: %s"namee)t)(*****************************************************************************)(* What the application draws *)(*****************************************************************************)letanchort=matcht.runwith|None->None|Somer->(matchr.statewith|Line_to{last;_}->Somelast|Circle_radius(c,_)->Somec|Arc_points(_::_asps)->Some(List.nthps(List.lengthps-1))|Second(_,_,b)->Someb|Dim_points(_::_asps)->Some(List.nthps(List.lengthps-1))|Insert_rotation(_,p,_)->Somep|Pan_points(Somep)|Dist_points(Somep)->Somep|_->None)letbox(ax,ay)(bx,by)=[D.Line((ax,ay),(bx,ay));D.Line((bx,ay),(bx,by));D.Line((bx,by),(ax,by));D.Line((ax,by),(ax,ay))]letpreviewtc=matcht.runwith|None->[]|Somer->(matchr.statewith|Line_to{last;_}->[D.Line(last,c)]|Circle_radius(ctr,d)->[D.Circle(ctr,G.distctrc/.ifdthen2.else1.)]|Arc_points[a]->[D.Line(a,c)]|Arc_points[a;b]->Option.to_list(arc3abc)|Second(_,objs,base)->List.filter_map(funid->Option.map(D.move_entity(G.subcbase))(entitytid))objs|Offset_sideid->(matchentitytidwithSomee->Result.to_list(Cad_edit.offsett.offset_distancee~side:c)|None->[])|Dim_points[a]->[D.Line(a,c)]|Dim_points[a;b]->[D.Dimension(a,b,c)]|Insert_pointname->[D.Insert(name,c,1.,0.)]|Insert_scale(name,p)->[D.Insert(name,p,1.,0.)]|Insert_rotation(name,p,s)->[D.Insert(name,p,s,G.anglepc)]|Pan_points(Somea)|Dist_points(Somea)->[D.Line(a,c)]|Zoom_corner(Somea)->boxac|_->[])letselectedt=matcht.runwith|Some{state=Select{picked;_};_}->picked|Some{state=Trim_pick(_,edges);_}->edges|Some{state=Base(_,objs)|Second(_,objs,_);_}->objs|Some{state=Offset_sideid|Fillet_second(id,_);_}->[id]|_->[]letselection_boxtc=matcht.runwithSome{state=Select{corner=Somea;_};_}->Some(a,c,fstc<fsta)|_->None