program gcodegenerator_v3; uses SysUtils, StrUtils, Math; type TPoint = record X, Y: double; end; type TStatorParams = record BaseDiameter: Double; BaseRadius: Double; NumberOfRays: Integer; RayShape: string; RayDiameter: Double; RayWidth: Double; RayHeight: Double; RayLength: Double; RayTopShape: string; RayTopDiameter: Double; RayTopWidth: Double; RayTopHeight: Double; RayCenterOffset: Double; WireDiameter: Double; NeedleDiameter: Double; PathClearance: Double; WorkSpeed :integer; end; var InputFileName, OutputFileName: string; StatorParams: TStatorParams; InFile, OutFile: TextFile; Line: string; CoilRadius,CoilWidth,CoilHeight :double; CurrentZ: double; coords: TPoint; normalizecoords: TPoint; i, j, k: Integer; CoilLength :double; CurrentCoilTurns, CoilTurnsSum :Integer; AngleBetweenRays :double; RequiredSpacing: Double; LayerNumber :Integer; MaxLayers :Integer; angle :double; MaxDepth,MaxPath :double; MoveForward:boolean; function ParseLine(Line: string): Boolean; var Parts: array of string; Value: string; TrimmedLine: string; begin TrimmedLine := Trim(Line); // Удаляем пробелы в начале и конце строки if Length(TrimmedLine) = 0 then begin exit(true); // Пустая строка - пропускаем end else if TrimmedLine[1] in [';','#'] then begin exit(true); // Строка комментария - пропускаем end; Parts := SplitString(TrimmedLine, '='); // Используем TrimmedLine Result := Length(Parts) = 2; if Result then begin Value := LowerCase(Trim(Parts[1])); // Приводим к нижнему регистру case Trim(Parts[0]) of 'base_dia': begin StatorParams.BaseDiameter := StrToFloat(Value); writeln('Base Diameter: ', StatorParams.BaseDiameter:8:2); writeln(); StatorParams.BaseRadius := StatorParams.BaseDiameter/2; writeln('Base Radius: ', StatorParams.BaseRadius:8:2); writeln(); end; 'num_rays': begin StatorParams.NumberOfRays := StrToInt(Value); writeln('Number of Rays: ', StatorParams.NumberOfRays); AngleBetweenRays := 360/StatorParams.NumberOfRays; writeln('Angle Between Rays: ', AngleBetweenRays); writeln(); end; 'ray_shape': begin if Pos(Value, 'circle, rect, superellipse') = 0 then raise Exception.Create('Invalid value for ray_shape: ' + Value); StatorParams.RayShape := Value; writeln('Ray Shape: ', StatorParams.RayShape); writeln(); end; 'ray_dia': begin StatorParams.RayDiameter := StrToFloat(Value); writeln('Ray Diameter: ', StatorParams.RayDiameter:8:2); writeln(); end; 'ray_w': begin StatorParams.RayWidth := StrToFloat(Value); writeln('Ray Width: ', StatorParams.RayWidth:8:2); writeln(); end; 'ray_h': begin StatorParams.RayHeight := StrToFloat(Value); writeln('Ray Height: ', StatorParams.RayHeight:8:2); writeln(); end; 'ray_len': begin StatorParams.RayLength := StrToFloat(Value); writeln('Ray Length: ', StatorParams.RayLength:8:2); writeln(); end; 'raytop_shape': begin if Pos(Value, 'circle, rect, superellipse') = 0 then raise Exception.Create('Invalid value for ray_top: ' + Value); StatorParams.RayTopShape := Value; writeln('Ray Top Shape: ', StatorParams.RayTopShape); end; 'raytop_dia': begin StatorParams.RayTopDiameter := StrToFloat(Value); writeln('Ray Top Diameter: ', StatorParams.RayTopDiameter:8:2); writeln(); end; 'raytop_w': begin StatorParams.RayTopWidth := StrToFloat(Value); writeln('Ray Top Width: ', StatorParams.RayTopWidth:8:2); writeln(); end; 'raytop_h': begin StatorParams.RayTopHeight := StrToFloat(Value); writeln('Ray Top Height: ', StatorParams.RayTopHeight:8:2); writeln(); end; 'ray_offset': begin StatorParams.RayCenterOffset := StrToFloat(Value); writeln('Ray Center Offset: ', StatorParams.RayCenterOffset:8:2); writeln(); end; 'wire_diameter': begin StatorParams.WireDiameter := StrToFloat(Value); writeln('Wire Diameter: ', StatorParams.WireDiameter:8:2); writeln(); end; 'needle_diameter': begin StatorParams.NeedleDiameter := StrToFloat(Value); writeln('Needle Diameter: ', StatorParams.NeedleDiameter:8:2); writeln(); end; 'path_clearance': begin StatorParams.PathClearance := StrToFloat(Value); writeln('Path Clearance: ', StatorParams.PathClearance:8:2); writeln(); end; 'work_speed': begin StatorParams.WorkSpeed := StrToInt(Value); writeln('Work Speed: ', StatorParams.WorkSpeed); writeln(); end; else Result := False; end; if not Result then begin writeln('Error: Unknown parameter: ', Parts[0]); exit; end; end; end; procedure ReadInputFile(); Begin // **Opening the input file** AssignFile(InFile, InputFileName); try Reset(InFile); // **This line opens the file for reading** while not EOF(InFile) do begin ReadLn(InFile, Line); if Length(Line) > 0 then if not ParseLine(Line) then writeln('Error: Invalid line: ', Line); end; except on E: Exception do begin writeln('Error opening or reading input file: ', E.Message); Halt(1); end; end; CloseFile(InFile); end; function CircleCoordinates(diameter, angleDegrees: Double): TPoint; var radius: Double; angleRadians: Double; begin radius := diameter / 2; // angleRadians := -1*angleDegrees * PI / 180; // Перевод градусов в радианы angleRadians := -1 * DegToRad(angleDegrees); Result.X := radius * Cos(angleRadians); Result.Y := radius * Sin(angleRadians); end; function CalculateAngle(opposite, adjacent: Double): Double; begin if adjacent = 0 then raise Exception.Create('Adjacent side cannot be zero'); // CalculateAngle := ArcTan(opposite / adjacent) * 180 / PI; CalculateAngle := RadToDeg(ArcTan(opposite / adjacent)); end; function NormalizeZ(radius: Real; thetaDeg: Real): Real; var thetaRad, alphaRad, xKas, yKas, mTang, bTang: Real; begin // 1. Преобразование угла из градусов в радианы. thetaRad := DegToRad(thetaDeg); // 2. Вычисление координат точки касания на окружности (относительно центра 0,0). xKas := radius * Cos(thetaRad); yKas := radius * Sin(thetaRad); // 3. Вычисление угла, перпендикулярного радиус-вектору (касательной). alphaRad := thetaRad + PI / 2; // 4. Вычисление углового коэффициента касательной. mTang := Tan(alphaRad); // 5. Вычисление свободного члена уравнения касательной (y = mTang * x + bTang). bTang := yKas - mTang * xKas; // 6. Вычисление координаты X точки пересечения касательной с осью X (y = 0). // 0 = mTang * x + bTang // x = -bTang / mTang if mTang = 0 then begin // Касательная параллельна оси X - нет точки пересечения. Возвращаем NaN. NormalizeZ := NaN; end else begin NormalizeZ := -bTang / mTang; end; end; function DepthCheck(thickness, lineLength, angleDeg: double): double; var xIntersection, distanceToIntersection, radius: double; begin xIntersection := (thickness/2 + thickness/2 * cos(DegToRad(angleDeg)))/sin(DegToRad(angleDeg)); distanceToIntersection := sqrt(sqr(xIntersection) + sqr(1.2)); radius := lineLength / (2 * tan(DegToRad(angleDeg) / 2)); DepthCheck := distanceToIntersection + radius; end; function CalculateMaxPath(length, width, angleDegrees: double): double; var topLeftX, topLeftY, rotatedX, rotatedY, angleRadians, distance: double; begin // Вычисляем координаты верхнего левого угла прямоугольника topLeftX := -length; topLeftY := width / 2; // Преобразуем угол в радианы (с учетом вращения по часовой стрелке) angleRadians := degToRad(-angleDegrees); // Вычисляем координаты повернутого нижнего левого угла второго прямоугольника rotatedX := -length * cos(angleRadians) - (-width / 2) * sin(angleRadians); rotatedY := -length * sin(angleRadians) + (-width / 2) * cos(angleRadians); // Вычисляем расстояние между точками distance := sqrt(sqr(rotatedX - topLeftX) + sqr(rotatedY - topLeftY)); // Возвращаем результат MaxPath := distance; end; begin // Command line argument handling if ParamCount < 2 then begin Writeln('Usage: GCodeGenerator '); Halt(1); end; InputFileName := ParamStr(1); OutputFileName := ParamStr(2); ReadInputFile(); // G-code generation AssignFile(OutFile, OutputFileName); try Rewrite(OutFile); // *** Your G-code generation logic here *** writeln(OutFile, '; G-code for stator winding'); writeln(OutFile, 'G90 ; Absolute coordinate system'); writeln(OutFile, 'G1 Y-10'); writeln(OutFile, 'G28 X Y Z'); writeln(OutFile, 'G1 X0 Y0'); //writeln(OutFile, 'G28 O'); //Home all "untrusted" axes writeln(OutFile); // Move to center of ray along Y axis and reset coordinate writeln(OutFile, 'G1 X', StatorParams.RayCenterOffset:0:2); writeln(OutFile, 'G92 X0'); //отводим иглу левее и ниже вершины луча CoilRadius:=(StatorParams.RayTopHeight/2+StatorParams.NeedleDiameter/2+StatorParams.PathClearance); CoilWidth:=(StatorParams.RayTopWidth+StatorParams.NeedleDiameter+StatorParams.PathClearance*2); CoilHeight:=(StatorParams.RayTopHeight+StatorParams.NeedleDiameter+StatorParams.PathClearance*2); writeln(OutFile, ';CoilRadius = ', CoilRadius:0:3); writeln(OutFile, ';CoilWidth = ', CoilWidth:0:3); writeln(OutFile, ';CoilHeight = ', CoilHeight:0:3); writeln(OutFile, 'G1 X', -1*CoilHeight/2:0:3, ' Y',-1*AngleBetweenRays/2:0:3); //приближаемся к основанию статора. CurrentZ:=(StatorParams.BaseDiameter/2)+StatorParams.PathClearance; writeln(OutFile, 'G1 Z', CurrentZ:0:2, ' F', StatorParams.WorkSpeed); //Встаём на паузу для того, чтобы привязать конец проволоки writeln(OutFile, 'M0'); writeln(OutFile, ';Start coil'); //Начинаем мотать катушку. //Считаем, сколько слоёв можем намотать. MaxPath:=CalculateMaxPath((StatorParams.RayLength+StatorParams.BaseRadius), StatorParams.RayWidth, AngleBetweenRays); // MaxLayers :=trunc(((MaxPath-RequiredSpacing)/2/StatorParams.WireDiameter)); MaxLayers :=round(((MaxPath-RequiredSpacing)/2/StatorParams.WireDiameter)); writeln(OutFile, ';MaxLayers ', MaxLayers); Inc(LayerNumber); //Двигаемся от центра к краю MoveForward:=true; if (StatorParams.RayShape = 'circle') then begin for j := 0 to CurrentCoilTurns do begin for i := 0 to 360 do begin coords := CircleCoordinates(CoilRadius*2, i+180); // writeln(OutFile,'CoilRadius*2= ',CoilRadius*2:0:5,' X=', coords.X:0:3, ' Y=', coords.Y:0:5); angle := CalculateAngle(coords.Y, CurrentZ); // writeln(OutFile, ';G1 X', coords.X:0:3, ' Y', angle:0:5,' Z', CurrentZ:0:5); writeln(OutFile, 'G1 X', coords.X:0:3, ' Y', angle:0:5,' Z', NormalizeZ(CurrentZ, angle):0:5); // writeln(OutFile); CurrentZ:=CurrentZ+StatorParams.WireDiameter/360; end; writeln(OutFile, ';Next coil.'); end; writeln(OutFile, ';Second coil'); Inc(LayerNumber); // RequiredSpacing:=StatorParams.RayWidth+StatorParams.NeedleDiameter+StatorParams.PathClearance*2+LayerNumber*StatorParams.WireDiameter*2; // writeln(OutFile, ';RequiredSpacing = ',RequiredSpacing:0:5); RequiredSpacing:=StatorParams.RayTopWidth; writeln(OutFile, ';RequiredSpacing = ',RequiredSpacing:0:5); RequiredSpacing:=RequiredSpacing+StatorParams.NeedleDiameter; writeln(OutFile, ';RequiredSpacing = ',RequiredSpacing:0:5); RequiredSpacing:=RequiredSpacing+StatorParams.PathClearance*2+LayerNumber*StatorParams.WireDiameter*2; writeln(OutFile, ';RequiredSpacing = ',RequiredSpacing:0:5); CurrentZ:=RequiredSpacing/(2 * tan(AngleBetweenRays*PI/180/2)); writeln(OutFile, ';CurrentZ = ',CurrentZ:0:3); end else if (StatorParams.RayShape = 'rect') then begin // writeln(OutFile, ';Rect? '); while (LayerNumber <= MaxLayers) do begin //writeln(OutFile, ';Layer ', LayerNumber); //Считаем длину катушки и количество витков //Для этого считаем необходимое расстояние между лучами. 2 диаметра провода + диаметр иглы + 2 зазора безопасности RequiredSpacing:=StatorParams.PathClearance*2+(LayerNumber-1)*StatorParams.WireDiameter*2+StatorParams.NeedleDiameter; //Используя необходимое расстояние, рассчитываем на какую, максимальную глубину может погрузиться иголка. (расстояние от центра статора) MaxDepth:=DepthCheck(StatorParams.RayWidth, RequiredSpacing, AngleBetweenRays); writeln(OutFile, ';RayWidth ', StatorParams.RayWidth:0:5); writeln(OutFile, ';RequiredSpacing ', RequiredSpacing:0:5); writeln(OutFile, ';AngleBetweenRays ', AngleBetweenRays:0:5); writeln(OutFile, ';MaxDepth ', MaxDepth:0:5); writeln(OutFile); //Если допустимая глубина меньше, чем диаметр основания, тогда длина катушки равна длине луча. //Если допустимая глубина больше, чем диаметр + длина луча, прекращаем движ. //Иначе длина катушки равна "радиус_основания + длина_луча - глубина) If (MaxDepth < (StatorParams.BaseRadius)) then CoilLength:=StatorParams.RayLength else if (MaxDepth > (StatorParams.RayLength+StatorParams.BaseRadius)) then break else CoilLength:=StatorParams.RayLength+StatorParams.BaseRadius-MaxDepth; writeln(OutFile, ';CoilLength ', CoilLength:0:5); // CurrentCoilTurns := ceil(CoilLength/StatorParams.WireDiameter); //Исходя из длины катушки считаем количество витков. И складываем сколько всего витков на катушке. CurrentCoilTurns := round(CoilLength/StatorParams.WireDiameter); writeln(OutFile, ';CurrentCoilTurns ', CurrentCoilTurns); CoilTurnsSum := CoilTurnsSum+CurrentCoilTurns; writeln(OutFile, ';CoilTurnsSum ', CoilTurnsSum); writeln(OutFile); //Перед каждым слоем нужно пересчитать координату Z write(OutFile,';RecalculateZ. CurrentZ=',CurrentZ:0:5); if (MoveForward = false) then CurrentZ:=StatorParams.RayLength+StatorParams.BaseRadius-StatorParams.WireDiameter/2 else CurrentZ:=StatorParams.RayLength+StatorParams.BaseRadius-StatorParams.WireDiameter*(CurrentCoilTurns-0.5); writeln(OutFile,'; NewZ=',CurrentZ:0:5); //Начинаем мотать. writeln(OutFile, ';We make ',CurrentCoilTurns, ' turns on ', LayerNumber, ' layer.' ); for j := 0 to CurrentCoilTurns do begin writeln(OutFile,';Coil№ = ',j); writeln(OutFile,'M117 ',j); //angle := CalculateAngle(CoilWidth/2, CurrentZ); angle := AngleBetweenRays/2; //Divide the path into 100 points //Move from left-down to left-up for k := 1 to 100 do begin angle := AngleBetweenRays/2; angle := ((-1*angle)+2*(angle/100*k)); writeln(OutFile, 'G1 X', -1*CoilHeight/2:0:3, ' Y', angle:0:5,' Z', NormalizeZ(CurrentZ, angle):0:5, ' ;CurrentZ= ',CurrentZ:0:5); // Top Left Corner end; if MoveForward then CurrentZ:=CurrentZ+StatorParams.WireDiameter/4 else CurrentZ:=CurrentZ-StatorParams.WireDiameter/4; // writeln(OutFile, 'G1 X', -1*CoilHeight/2:0:3, ' Y', angle:0:5,' Z', NormalizeZ(CurrentZ, angle):0:5); // Top Left Corner // if MoveForward then CurrentZ:=CurrentZ+StatorParams.WireDiameter/4 else CurrentZ:=CurrentZ-StatorParams.WireDiameter/4; //writeln(OutFile, 'M0'); // angle := CalculateAngle(CoilWidth/2, CurrentZ); angle := AngleBetweenRays/2; writeln(OutFile, 'G1 X', CoilHeight/2:0:3, ' Y', angle:0:5,' Z', NormalizeZ(CurrentZ, angle):0:5, ' ;CurrentZ= ',CurrentZ:0:5); // Top Right Corner if MoveForward then CurrentZ:=CurrentZ+StatorParams.WireDiameter/4 else CurrentZ:=CurrentZ-StatorParams.WireDiameter/4; //writeln(OutFile, 'M0'); //Divide the path into 100 points //Move from left-down to rt-up for k := 1 to 100 do begin angle := AngleBetweenRays/2; angle := (angle-(2*(angle/100*k))); writeln(OutFile, 'G1 X', CoilHeight/2:0:3, ' Y', angle:0:5,' Z', NormalizeZ(CurrentZ, angle):0:5, ' ;CurrentZ= ',CurrentZ:0:5); // Bottom Right Corner end; if MoveForward then CurrentZ:=CurrentZ+StatorParams.WireDiameter/4 else CurrentZ:=CurrentZ-StatorParams.WireDiameter/4; // angle := CalculateAngle(-1*CoilWidth/2, CurrentZ); // writeln(OutFile, 'G1 X', CoilHeight/2:0:3, ' Y', -angle:0:5,' Z', NormalizeZ(CurrentZ, angle):0:5); // Bottom Right Corner // if MoveForward then CurrentZ:=CurrentZ+StatorParams.WireDiameter/4 else CurrentZ:=CurrentZ-StatorParams.WireDiameter/4; //writeln(OutFile, 'M0'); // angle := CalculateAngle(CoilWidth/2, CurrentZ); angle := AngleBetweenRays/2; writeln(OutFile, 'G1 X', -1*CoilHeight/2:0:3, ' Y', -angle:0:5,' Z', NormalizeZ(CurrentZ, angle):0:5, ' ;CurrentZ= ',CurrentZ:0:5); // Bottom Left Corner if MoveForward then CurrentZ:=CurrentZ+StatorParams.WireDiameter/4 else CurrentZ:=CurrentZ-StatorParams.WireDiameter/4; //writeln(OutFile, 'M0'); end; writeln(OutFile, 'M0'); // writeln(OutFile, 'G91'); // writeln(OutFile, 'G1 Y50'); // writeln(OutFile, 'G90'); Inc(LayerNumber); CoilWidth:=CoilWidth+StatorParams.WireDiameter*2; MoveForward:= not MoveForward; writeln(OutFile,';MoveForward: ', MoveForward); end; (* writeln(OutFile); writeln(OutFile, ';Second coil'); Inc(LayerNumber); RequiredSpacing:=StatorParams.PathClearance*2+LayerNumber*StatorParams.WireDiameter*2+StatorParams.NeedleDiameter; writeln(OutFile, ';RequiredSpacing = ',RequiredSpacing:0:5); For i:=1 to 20 do begin LayerNumber:=i; writeln(OutFile, ';Layer ', i); RequiredSpacing:=StatorParams.PathClearance*2+(LayerNumber-1)*StatorParams.WireDiameter*2+StatorParams.NeedleDiameter; writeln(OutFile, ';RequiredSpacing ', RequiredSpacing:0:5); MaxDepth:=DepthCheck(StatorParams.RayWidth, RequiredSpacing, AngleBetweenRays); writeln(OutFile, ';MaxDepth ', MaxDepth:0:5); If (MaxDepth < (StatorParams.BaseRadius)) then CoilLength:=StatorParams.RayLength else if (MaxDepth > (StatorParams.RayLength+StatorParams.BaseRadius)) then break else CoilLength:=StatorParams.RayLength+StatorParams.BaseRadius-MaxDepth; writeln(OutFile, ';CoilLength ', CoilLength:0:5); // CurrentCoilTurns := round(CoilLength/StatorParams.WireDiameter); // CurrentCoilTurns := ceil(CoilLength/StatorParams.WireDiameter); CurrentCoilTurns := floor(CoilLength/StatorParams.WireDiameter); writeln(OutFile, ';CurrentCoilTurns ', CurrentCoilTurns); CoilTurnsSum := CoilTurnsSum+CurrentCoilTurns; writeln(OutFile, ';CoilTurnsSum ', CoilTurnsSum); writeln(OutFile); end;*) end; // *** Your G-code generation logic here *** except on E: Exception do begin writeln('Error writing to output file: ', E.Message); Halt(1); end; end; CloseFile(OutFile); writeln('G-code generated to: ', OutputFileName); writeln('Press Enter... '); Readln; end.