Простой векторный 2D редактор для черчения
Модератор: Модераторы
А что будет делать конечный инструмент? Вижу ГИС картинки.
PS^ Inkscape может помочь сделать приличные кнопки.
PS^ Inkscape может помочь сделать приличные кнопки.
Решил не мудрствовать и добавил сначала индикацию состояния кнопок модификаторов и текущего инструмента и посмотрев на это безобразие (состояние Ctrl Alt и Shift действительно ингода "залипает" ) Сделал явный показ списка фигур
(И само собой теперь можно кликом по списку ставить и снимать выделение )
Дополнение вполне работает но нужно "навести блеск" в разных мелочах.
Добавлено спустя 5 часов 40 минут 31 секунду:
Пока что есть что-такое...

1 По центру и справа "настоящая ГИС " ( на основе общедоступных интернет сервисов )
2 Слева простой "редактор структурных диаграмм" (тоже векторный но значительно более примитивный )...
3 Сверху панель управления и интеграции + инструментарий для создания презентаций .
А вот снизу будет "промежуточное звено" простой векторный 2D редактор для черчения.
(По сути "векторный блокнот" для упрощенного черчения (возможно частично поверх ГИС карт или упрощенных понятных схем с привязкой к местности ))
Зы
Извиняюсь что не ответил сразу ! ( Не заметили переход на новую станицу форума )
(И само собой теперь можно кликом по списку ставить и снимать выделение )
Дополнение вполне работает но нужно "навести блеск" в разных мелочах.
Добавлено спустя 5 часов 40 минут 31 секунду:
Вообщем все это часть проекта среды для "поддержки мозгового штурма " и создания презентаций на около-мелиорационные темы (Задача обеспечить наглядный выбор вариантов для фермеров, агрономов, руководителей мелиоративных объектов и т.п. - по сути продвинутый вариант инфографики с данными схемами и гис-привязкой (возможно на следующем этапе будет расчетный болк,таблицы, генератор отсчетов и т.п. но пока задача создать эффектную продвинутую (с опорой на реальные разработки ) и наглядную "показуху" некого "наукоемкого процесса".))Sharfik писал(а):А что будет делать конечный инструмент? Вижу ГИС картинки.
Пока что есть что-такое...

1 По центру и справа "настоящая ГИС " ( на основе общедоступных интернет сервисов )
2 Слева простой "редактор структурных диаграмм" (тоже векторный но значительно более примитивный )...
3 Сверху панель управления и интеграции + инструментарий для создания презентаций .
А вот снизу будет "промежуточное звено" простой векторный 2D редактор для черчения.
(По сути "векторный блокнот" для упрощенного черчения (возможно частично поверх ГИС карт или упрощенных понятных схем с привязкой к местности ))
Зы
Извиняюсь что не ответил сразу ! ( Не заметили переход на новую станицу форума )
Тут у меня очедная "вынужденная передислокация" из "темного царства ангины" (Киева) почти отогрелся и постепенно обустраиваюсь.
Надеюсь что скоро выложу очередную сборку .
Надеюсь что скоро выложу очередную сборку .

И так обещанная свежая сборка (на самом деле тестовых сборок было уже несколько но эта более менее полезная и стабильная)
>>>>Min_VGED_BIN_B_0_04_579_7.7z
>>>>Min_VGED_SRC_B_0_04_579_7.7z
Добавил список фигур(можно снимать и устанавливать выделение в "ручном режиме"), удобное переключение режимов "прямого выбора фигур"(немного недоделано но добавление списка и "ручного режима " снимает большую часть возможных проблем ) и скроллбар в панели инструментов
Последний раз редактировалось Alex2013 17.02.2026 19:55:47, всего редактировалось 2 раза.
Пожалуйста !
(надеюсь на помощь в тестировании)
Зы
В бинарник включил тестовые изображения. ( "избранная халтура"
)
Зы
В бинарник включил тестовые изображения. ( "избранная халтура"
Первая версия конвертера svg2ged (ged формат записи моего редактора )
( Еще не отлаживал что-то так что может содержать ошибки и частично не работать )
1 Поддерживаемые элементы SVG:
<rect> → TRectangle (или TRoundRectangle если есть rx/ry)
<circle> → TEllipse
<ellipse> → TEllipse
<line> → TLine
<polyline> → TPolyLine
<polygon> → TPolygon
<text> → TText
2 Поддержка стилей:
Цвета (RGB и HEX)
Толщина линий
Стили штрихов
Заливки
Трансформации (translate)
3 Ограничения:
Не поддерживаются сложные пути <path>
Не поддерживаются градиенты
Не поддерживаются маски и фильтры
( Еще не отлаживал что-то так что может содержать ошибки и частично не работать )
1 Поддерживаемые элементы SVG:
<rect> → TRectangle (или TRoundRectangle если есть rx/ry)
<circle> → TEllipse
<ellipse> → TEllipse
<line> → TLine
<polyline> → TPolyLine
<polygon> → TPolygon
<text> → TText
2 Поддержка стилей:
Цвета (RGB и HEX)
Толщина линий
Стили штрихов
Заливки
Трансформации (translate)
3 Ограничения:
Не поддерживаются сложные пути <path>
Не поддерживаются градиенты
Не поддерживаются маски и фильтры
Код: Выделить всё
Нерабочий код выкинул ...
Последний раз редактировалось Alex2013 21.02.2026 17:19:16, всего редактировалось 1 раз.
Слегка исправленная версия конвертера svg2ged (собирается и немного работает )
Было Svg
Стало ged

Код: Выделить всё
program svg2ged;
{$mode objfpc}{$H+}
uses
Classes, SysUtils, DOM, XMLRead, fpjson,
Math, Graphics;
type
TDPoint = record
x, y: double;
end;
TFigureData = record
ClassName: string;
Points: array of TDPoint;
PenColor: Integer;
PenWidth: Integer;
PenStyle: Integer; // 0..5
BrushColor: Integer;
BrushStyle: Integer; // 0..7
Rounding: Integer;
Text: string;
FontName: string;
FontSize: Integer;
end;
TFiguresArray = array of TFigureData;
// Вспомогательная функция для безопасного получения значения атрибута
function GetAttrValue(Node: TDOMNode; const AttrName: string): string;
var
Attr: TDOMNode;
begin
Result := '';
if Node = nil then Exit;
Attr := Node.Attributes.GetNamedItem(AttrName);
if Attr <> nil then
Result := Attr.NodeValue;
end;
function ColorToInt(const AColor: string): Integer;
var
R, G, B: Integer;
Temp: string;
begin
Result := clBlack;
if AColor = '' then Exit;
// Парсим rgb(r,g,b)
if Pos('rgb', LowerCase(AColor)) = 1 then
begin
Temp := Copy(AColor, 5, Length(AColor) - 5);
R := StrToIntDef(Trim(Copy(Temp, 1, Pos(',', Temp) - 1)), 0);
Delete(Temp, 1, Pos(',', Temp));
G := StrToIntDef(Trim(Copy(Temp, 1, Pos(',', Temp) - 1)), 0);
Delete(Temp, 1, Pos(',', Temp));
B := StrToIntDef(Trim(Temp), 0);
Result := RGBToColor(R, G, B);
end
// Парсим hex #RRGGBB
else if Pos('#', AColor) = 1 then
begin
Temp := Copy(AColor, 2, 6);
if Length(Temp) = 6 then
begin
R := StrToIntDef('$' + Copy(Temp, 1, 2), 0);
G := StrToIntDef('$' + Copy(Temp, 3, 2), 0);
B := StrToIntDef('$' + Copy(Temp, 5, 2), 0);
Result := RGBToColor(R, G, B);
end;
end
// Парсим именованные цвета (упрощенно)
else if AColor = 'black' then Result := clBlack
else if AColor = 'white' then Result := clWhite
else if AColor = 'red' then Result := clRed
else if AColor = 'green' then Result := clGreen
else if AColor = 'blue' then Result := clBlue
else if AColor = 'yellow' then Result := clYellow
else if AColor = 'gray' then Result := clGray
else if AColor = 'silver' then Result := clSilver
else if AColor = 'maroon' then Result := clMaroon
else if AColor = 'purple' then Result := clPurple
else if AColor = 'fuchsia' then Result := clFuchsia
else if AColor = 'lime' then Result := clLime
else if AColor = 'olive' then Result := clOlive
else if AColor = 'navy' then Result := clNavy
else if AColor = 'teal' then Result := clTeal
else if AColor = 'aqua' then Result := clAqua;
end;
function ParseStrokeStyle(const AStrokeDashArray: string): Integer;
begin
Result := 0; // psSolid по умолчанию
if AStrokeDashArray = '' then Exit;
if Pos('none', LowerCase(AStrokeDashArray)) > 0 then
Result := 1 // psClear
else if Pos('dash', LowerCase(AStrokeDashArray)) > 0 then
begin
if Pos('dot', LowerCase(AStrokeDashArray)) > 0 then
begin
if Pos('dot dot', LowerCase(AStrokeDashArray)) > 0 then
Result := 5 // psDashDotDot
else
Result := 4; // psDashDot
end
else
Result := 3; // psDash
end
else if Pos('dot', LowerCase(AStrokeDashArray)) > 0 then
Result := 2; // psDot
end;
function ParseFillStyle(const AFill: string): Integer;
begin
Result := 1; // bsClear по умолчанию
if AFill = '' then Exit;
if (AFill <> 'none') and (AFill <> '') then
Result := 0; // Если есть цвет - Solid
end;
procedure ParseTransform(const ATransform: string; out TX, TY: Double);
var
Temp: string;
P: Integer;
begin
TX := 0; TY := 0;
if ATransform = '' then Exit;
Temp := LowerCase(ATransform);
P := Pos('translate(', Temp);
if P > 0 then
begin
Temp := Copy(Temp, P + 10, Length(Temp));
P := Pos(')', Temp);
if P > 0 then
begin
Temp := Copy(Temp, 1, P - 1);
P := Pos(',', Temp);
if P > 0 then
begin
TX := StrToFloatDef(Trim(Copy(Temp, 1, P - 1)), 0);
TY := StrToFloatDef(Trim(Copy(Temp, P + 1, Length(Temp))), 0);
end
else
TX := StrToFloatDef(Trim(Temp), 0);
end;
end;
end;
function SVGToGED(const ASVGFile: string): TFiguresArray;
var
Doc: TXMLDocument;
Node, Child: TDOMNode;
i: Integer;
Figure: TFigureData;
Points: TStringList;
PointStr, Temp: string;
j, k: Integer;
X, Y, TX, TY: Double;
AttrValue: string;
begin
Result := nil;
Points := TStringList.Create;
try
ReadXMLFile(Doc, ASVGFile);
try
Node := Doc.DocumentElement.FirstChild;
while Assigned(Node) do
begin
if Node.NodeName = 'rect' then
begin
SetLength(Result, Length(Result) + 1);
Figure.ClassName := 'TRectangle';
SetLength(Figure.Points, 2);
X := StrToFloatDef(GetAttrValue(Node, 'x'), 0);
Y := StrToFloatDef(GetAttrValue(Node, 'y'), 0);
Figure.Points[0].x := X;
Figure.Points[0].y := Y;
Figure.Points[1].x := X + StrToFloatDef(GetAttrValue(Node, 'width'), 100);
Figure.Points[1].y := Y + StrToFloatDef(GetAttrValue(Node, 'height'), 100);
Figure.PenColor := ColorToInt(GetAttrValue(Node, 'stroke'));
Figure.PenWidth := StrToIntDef(GetAttrValue(Node, 'stroke-width'), 1);
Figure.PenStyle := ParseStrokeStyle(GetAttrValue(Node, 'stroke-dasharray'));
Figure.BrushColor := ColorToInt(GetAttrValue(Node, 'fill'));
Figure.BrushStyle := ParseFillStyle(GetAttrValue(Node, 'fill'));
// Проверяем на скругленный прямоугольник
AttrValue := GetAttrValue(Node, 'rx');
if AttrValue <> '' then
begin
Figure.ClassName := 'TRoundRectangle';
Figure.Rounding := StrToIntDef(AttrValue, 10);
end;
Result[High(Result)] := Figure;
end
else if Node.NodeName = 'circle' then
begin
SetLength(Result, Length(Result) + 1);
Figure.ClassName := 'TEllipse';
SetLength(Figure.Points, 2);
X := StrToFloatDef(GetAttrValue(Node, 'cx'), 0);
Y := StrToFloatDef(GetAttrValue(Node, 'cy'), 0);
Temp := GetAttrValue(Node, 'r');
Figure.Points[0].x := X - StrToFloatDef(Temp, 50);
Figure.Points[0].y := Y - StrToFloatDef(Temp, 50);
Figure.Points[1].x := X + StrToFloatDef(Temp, 50);
Figure.Points[1].y := Y + StrToFloatDef(Temp, 50);
Figure.PenColor := ColorToInt(GetAttrValue(Node, 'stroke'));
Figure.PenWidth := StrToIntDef(GetAttrValue(Node, 'stroke-width'), 1);
Figure.PenStyle := ParseStrokeStyle(GetAttrValue(Node, 'stroke-dasharray'));
Figure.BrushColor := ColorToInt(GetAttrValue(Node, 'fill'));
Figure.BrushStyle := ParseFillStyle(GetAttrValue(Node, 'fill'));
Result[High(Result)] := Figure;
end
else if Node.NodeName = 'ellipse' then
begin
SetLength(Result, Length(Result) + 1);
Figure.ClassName := 'TEllipse';
SetLength(Figure.Points, 2);
X := StrToFloatDef(GetAttrValue(Node, 'cx'), 0);
Y := StrToFloatDef(GetAttrValue(Node, 'cy'), 0);
Figure.Points[0].x := X - StrToFloatDef(GetAttrValue(Node, 'rx'), 50);
Figure.Points[0].y := Y - StrToFloatDef(GetAttrValue(Node, 'ry'), 50);
Figure.Points[1].x := X + StrToFloatDef(GetAttrValue(Node, 'rx'), 50);
Figure.Points[1].y := Y + StrToFloatDef(GetAttrValue(Node, 'ry'), 50);
Figure.PenColor := ColorToInt(GetAttrValue(Node, 'stroke'));
Figure.PenWidth := StrToIntDef(GetAttrValue(Node, 'stroke-width'), 1);
Figure.PenStyle := ParseStrokeStyle(GetAttrValue(Node, 'stroke-dasharray'));
Figure.BrushColor := ColorToInt(GetAttrValue(Node, 'fill'));
Figure.BrushStyle := ParseFillStyle(GetAttrValue(Node, 'fill'));
Result[High(Result)] := Figure;
end
else if Node.NodeName = 'line' then
begin
SetLength(Result, Length(Result) + 1);
Figure.ClassName := 'TLine';
SetLength(Figure.Points, 2);
Figure.Points[0].x := StrToFloatDef(GetAttrValue(Node, 'x1'), 0);
Figure.Points[0].y := StrToFloatDef(GetAttrValue(Node, 'y1'), 0);
Figure.Points[1].x := StrToFloatDef(GetAttrValue(Node, 'x2'), 100);
Figure.Points[1].y := StrToFloatDef(GetAttrValue(Node, 'y2'), 100);
Figure.PenColor := ColorToInt(GetAttrValue(Node, 'stroke'));
Figure.PenWidth := StrToIntDef(GetAttrValue(Node, 'stroke-width'), 1);
Figure.PenStyle := ParseStrokeStyle(GetAttrValue(Node, 'stroke-dasharray'));
Figure.BrushStyle := 1; // bsClear
Result[High(Result)] := Figure;
end
else if (Node.NodeName = 'polyline') or (Node.NodeName = 'polygon') then
begin
SetLength(Result, Length(Result) + 1);
if Node.NodeName = 'polyline' then
Figure.ClassName := 'TPolyLine'
else
Figure.ClassName := 'TPolygon';
Temp := GetAttrValue(Node, 'points');
Points.Clear;
// Простой парсинг точек
Temp := StringReplace(Temp, ',', ' ', [rfReplaceAll]);
Temp := StringReplace(Temp, '-', ' -', [rfReplaceAll]);
Temp := Trim(Temp);
while Pos(' ', Temp) > 0 do
Temp := StringReplace(Temp, ' ', ' ', [rfReplaceAll]);
Points.DelimitedText := Temp;
SetLength(Figure.Points, Points.Count div 2);
for j := 0 to (Points.Count div 2) - 1 do
begin
Figure.Points[j].x := StrToFloatDef(Points[j * 2], 0);
Figure.Points[j].y := StrToFloatDef(Points[j * 2 + 1], 0);
end;
Figure.PenColor := ColorToInt(GetAttrValue(Node, 'stroke'));
Figure.PenWidth := StrToIntDef(GetAttrValue(Node, 'stroke-width'), 1);
Figure.PenStyle := ParseStrokeStyle(GetAttrValue(Node, 'stroke-dasharray'));
Figure.BrushColor := ColorToInt(GetAttrValue(Node, 'fill'));
Figure.BrushStyle := ParseFillStyle(GetAttrValue(Node, 'fill'));
Result[High(Result)] := Figure;
end
else if Node.NodeName = 'text' then
begin
SetLength(Result, Length(Result) + 1);
Figure.ClassName := 'TText';
SetLength(Figure.Points, 1);
Figure.Points[0].x := StrToFloatDef(GetAttrValue(Node, 'x'), 0);
Figure.Points[0].y := StrToFloatDef(GetAttrValue(Node, 'y'), 0);
// Применяем трансформацию
ParseTransform(GetAttrValue(Node, 'transform'), TX, TY);
Figure.Points[0].x := Figure.Points[0].x + TX;
Figure.Points[0].y := Figure.Points[0].y + TY;
// Получаем текст
if Node.FirstChild <> nil then
Figure.Text := Node.FirstChild.NodeValue;
// Парсим стили
Figure.PenColor := ColorToInt(GetAttrValue(Node, 'fill'));
Figure.FontName := GetAttrValue(Node, 'font-family');
if Figure.FontName = '' then
Figure.FontName := 'Arial';
AttrValue := GetAttrValue(Node, 'font-size');
if AttrValue <> '' then
Figure.FontSize := StrToIntDef(AttrValue, 12)
else
Figure.FontSize := 12;
Figure.PenWidth := 1;
Figure.BrushStyle := 1; // bsClear
Result[High(Result)] := Figure;
end;
Node := Node.NextSibling;
end;
finally
Doc.Free;
end;
finally
Points.Free;
end;
end;
procedure SaveAsGED(const AFigures: TFiguresArray; const AFileName: string);
var
Data: TJSONObject;
FiguresArr: TJSONArray;
PointsArr: TJSONArray;
i, j: Integer;
F: TFigureData;
JSONObj: TJSONObject;
ViewState: TJSONObject;
begin
Data := TJSONObject.Create;
try
FiguresArr := TJSONArray.Create;
for i := 0 to High(AFigures) do
begin
F := AFigures[i];
JSONObj := TJSONObject.Create;
JSONObj.Add('Class', F.ClassName);
PointsArr := TJSONArray.Create;
for j := 0 to High(F.Points) do
PointsArr.Add(TJSONObject.Create(['x', F.Points[j].x, 'y', F.Points[j].y]));
JSONObj.Add('Points', PointsArr);
JSONObj.Add('PenStyle', F.PenStyle);
JSONObj.Add('PenColor', F.PenColor);
JSONObj.Add('PenWidth', F.PenWidth);
JSONObj.Add('BrushStyle', F.BrushStyle);
JSONObj.Add('BrushColor', F.BrushColor);
if F.ClassName = 'TRoundRectangle' then
JSONObj.Add('Rounding', F.Rounding);
if F.ClassName = 'TText' then
begin
JSONObj.Add('Text', F.Text);
JSONObj.Add('FontName', F.FontName);
JSONObj.Add('BaseFontSize', F.FontSize);
end;
FiguresArr.Add(JSONObj);
end;
Data.Add('GraphicEditor', FiguresArr);
// Добавляем состояние вида
ViewState := TJSONObject.Create;
ViewState.Add('scale', 1.0);
ViewState.Add('offsetX', 0.0);
ViewState.Add('offsetY', 0.0);
Data.Add('viewState', ViewState);
with TStringList.Create do
begin
Text := Data.FormatJSON;
SaveToFile(AFileName);
Free;
end;
finally
Data.Free;
end;
end;
var
InputFile, OutputFile: string;
Figures: TFiguresArray;
begin
if ParamCount < 1 then
begin
Writeln('SVG to GED Converter');
Writeln('Usage: svg2ged.exe input.svg [output.ged]');
Halt(1);
end;
InputFile := ParamStr(1);
if not FileExists(InputFile) then
begin
Writeln('Error: File not found - ', InputFile);
Halt(1);
end;
if ParamCount >= 2 then
OutputFile := ParamStr(2)
else
OutputFile := ChangeFileExt(InputFile, '.ged');
try
Writeln('Converting ', InputFile, ' -> ', OutputFile);
Figures := SVGToGED(InputFile);
SaveAsGED(Figures, OutputFile);
Writeln('Done. Converted ', Length(Figures), ' figures.');
except
on E: Exception do
Writeln('Error: ', E.Message);
end;
end.
Код: Выделить всё
<?xml version="1.0"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg"
width="467" height="462">
<rect x="80" y="60" width="250" height="250" rx="20"
fill="#ff0000" style="stroke:#000000;stroke-width:2px;" />
<rect x="141" y="121" width="251" height="251" rx="4"
fill="#0000ff" style=" stroke:#000000; stroke-width:2px;
fill-opacity:0.7;" />
<rect x="140" y="120" width="250" height="250" rx="40"
fill="#00ff00" style="stroke:#0000cc; stroke-width:5px;
fill-opacity:1.0;" />
<circle cx="100" cy="100" r="50" stroke="black"
stroke-width="5" fill="red" />
<polygon
points=" 60,100 100,180 140,140 180,180 220,100"
fill="green" stroke-width="2" />
</svg>
Код: Выделить всё
{
"GraphicEditor" : [
{
"Class" : "TRoundRectangle",
"Points" : [
{
"x" : 8.0000000000000000E+001,
"y" : 6.0000000000000000E+001
},
{
"x" : 3.3000000000000000E+002,
"y" : 3.1000000000000000E+002
}
],
"PenStyle" : 0,
"PenColor" : 0,
"PenWidth" : 1,
"BrushStyle" : 0,
"BrushColor" : 255,
"Rounding" : 20
},
{
"Class" : "TRoundRectangle",
"Points" : [
{
"x" : 1.4100000000000000E+002,
"y" : 1.2100000000000000E+002
},
{
"x" : 3.9200000000000000E+002,
"y" : 3.7200000000000000E+002
}
],
"PenStyle" : 0,
"PenColor" : 0,
"PenWidth" : 1,
"BrushStyle" : 0,
"BrushColor" : 16711680,
"Rounding" : 4
},
{
"Class" : "TRoundRectangle",
"Points" : [
{
"x" : 1.4000000000000000E+002,
"y" : 1.2000000000000000E+002
},
{
"x" : 3.9000000000000000E+002,
"y" : 3.7000000000000000E+002
}
],
"PenStyle" : 0,
"PenColor" : 0,
"PenWidth" : 1,
"BrushStyle" : 0,
"BrushColor" : 65280,
"Rounding" : 40
},
{
"Class" : "TEllipse",
"Points" : [
{
"x" : 5.0000000000000000E+001,
"y" : 5.0000000000000000E+001
},
{
"x" : 1.5000000000000000E+002,
"y" : 1.5000000000000000E+002
}
],
"PenStyle" : 0,
"PenColor" : 0,
"PenWidth" : 5,
"BrushStyle" : 0,
"BrushColor" : 255
},
{
"Class" : "TPolygon",
"Points" : [
{
"x" : 6.0000000000000000E+001,
"y" : 1.0000000000000000E+002
},
{
"x" : 1.0000000000000000E+002,
"y" : 1.8000000000000000E+002
},
{
"x" : 1.4000000000000000E+002,
"y" : 1.4000000000000000E+002
},
{
"x" : 1.8000000000000000E+002,
"y" : 1.8000000000000000E+002
},
{
"x" : 2.2000000000000000E+002,
"y" : 1.0000000000000000E+002
}
],
"PenStyle" : 0,
"PenColor" : 0,
"PenWidth" : 2,
"BrushStyle" : 0,
"BrushColor" : 32768
}
],
"viewState" : {
"scale" : 1.0000000000000000E+000,
"offsetX" : 0.0000000000000000E+000,
"offsetY" : 0.0000000000000000E+000
}
}

