I’ve made a little component that can help you achieve what you’re looking for:
SingleLineArt.gha (11 KB)
SingleLineArt.gh (8.0 KB)
Maryam.jpg (43.0 KB)
Sourse Code:
namespace SingleLineArt
{
public class SingleLineArtGhc : GH_Component
{
private GH_MemoryBitmap _bmpMemory;
private Transform _scale;
private Transform _orient;
private Rectangle3d _rec;
private Point3dList _pts;
public SingleLineArtGhc()
: base("SingleLineArt", "SingleLineArt", "SingleLineArt", "Extra", "SingleLineArt") { }
protected override void RegisterInputParams(GH_InputParamManager pManager)
{
pManager.AddBooleanParameter("Reset", "Reset", "Reset", GH_ParamAccess.item);
var paramFilePath = new Param_FilePath
{
FileFilter = "All image files|*.bmp;*.jpg;*.jpeg;*.png;*.tif;*.tiff",
ExpireOnFileEvent = true
};
pManager.AddParameter(paramFilePath, "File", "File", "Location of image file", GH_ParamAccess.item);
pManager.AddRectangleParameter("Rectangle", "Rectangle", "Rectangle", GH_ParamAccess.item);
pManager.AddCurveParameter("Curve", "Curve", "Starting Curve", GH_ParamAccess.item);
pManager.AddIntegerParameter("Start", "Start", "Number of starting circles", GH_ParamAccess.item);
pManager.AddIntegerParameter("End", "End", "Maximum number of circles", GH_ParamAccess.item);
pManager.AddNumberParameter("Min", "Min", "Smallest Circle", GH_ParamAccess.item);
pManager.AddNumberParameter("Max", "Max", "Largest Circle", GH_ParamAccess.item);
pManager[0].Optional = true;
}
protected override void RegisterOutputParams(GH_OutputParamManager pManager)
{
pManager.AddPointParameter("Points", "Points", "Points", GH_ParamAccess.list);
pManager.AddNumberParameter("Radiuses", "Radiuses", "Radiuses", GH_ParamAccess.list);
}
protected override void SolveInstance(IGH_DataAccess DA)
{
var reset = false;
DA.GetData(0, ref reset);
if (reset || _pts is null)
{
string path = null;
if (!DA.GetData(1, ref path) || !File.Exists(path))
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Error, "Image file does not exist");
return;
}
DA.GetData(2, ref _rec);
var bmp = new Bitmap(path);
var imgRec = new Rectangle3d(Plane.WorldXY, bmp.Width, bmp.Height);
_bmpMemory?.Release(false);
_bmpMemory = new GH_MemoryBitmap(bmp, WrapMode.Clamp);
var plane1 = _rec.Plane;
var plane2 = imgRec.Plane;
plane1.Origin = _rec.Center;
plane2.Origin = imgRec.Center;
_scale = Transform.Scale(plane1, imgRec.X.Length / _rec.X.Length, -imgRec.Y.Length / _rec.Y.Length, 1.0);
_orient = Transform.PlaneToPlane(plane1, plane2);
Curve curve = null;
DA.GetData(3, ref curve);
var start = 0;
DA.GetData(4, ref start);
curve.DivideByCount(start, true, out var pts);
_pts = new Point3dList(pts);
}
var rads = new double[_pts.Count];
var min = double.NaN;
var max = double.NaN;
DA.GetData(6, ref min);
DA.GetData(7, ref max);
Parallel.For(0, _pts.Count, i =>
{
var pt = _pts[i];
pt.Transform(_scale);
pt.Transform(_orient);
var red = Convert.ToDouble(_bmpMemory.R(Convert.ToInt32(pt.X), Convert.ToInt32(pt.Y))) / 255;
rads[i] = red * (max - min) + min;
});
var counts = new int[_pts.Count];
var vecs = new Vector3d[_pts.Count];
var rTree = RTree.CreateFromPointArray(_pts);
var r = rads.Max() * 2;
var indices = new List<int>[_pts.Count];
Parallel.For(0, _pts.Count, i =>
{
indices[i] = new List<int>();
rTree.Search(new Sphere(_pts[i], r), (sender, args) =>
{
if (args.Id > i) indices[i].Add(args.Id);
});
});
Parallel.For(0, _pts.Count, (i) =>
{
foreach (var j in indices[i])
{
var vector = _pts[i] - _pts[j];
var d2 = vector.SquareLength;
if (d2 > Math.Pow(rads[i] + rads[j], 2)) continue;
counts[i]++;
counts[j]++;
vector.Unitize();
vector *= (rads[i] + rads[j] - Math.Sqrt(d2)) * 0.5;
vecs[i] += vector;
vecs[j] -= vector;
}
});
Parallel.For(0, _pts.Count, i =>
{
if (counts[i] == 0) return;
vecs[i] /= counts[i];
if (Math.Min(_pts[i].X, _rec.Width - _pts[i].X) < rads[i]) vecs[i].X = 0;
if (Math.Min(_pts[i].Y, _rec.Height - _pts[i].Y) < rads[i]) vecs[i].Y = 0;
_pts[i] += vecs[i];
});
DA.SetDataList(0, _pts);
DA.SetDataList(1, rads);
var end = 0;
DA.GetData(5, ref end);
if (_pts.Count > end) return;
var newPts = new List<Point3d>();
var newIndices = new List<int>();
for (var i = 0; i < _pts.Count - 1; i++)
{
var vec = _pts[i] - _pts[i + 1];
if (vec.SquareLength < Math.Pow((rads[i] + rads[i + 1]), 2)) continue;
newIndices.Add(i + 1 + newIndices.Count);
newPts.Add((_pts[i] + _pts[i + 1]) * 0.5);
}
for (var i = 0; i < newIndices.Count; i++)
_pts.Insert(newIndices[i], newPts[i]);
}
protected override Bitmap Icon => null;
public override Guid ComponentGuid => new Guid("83b6bf0b-0312-4184-935c-b48d952f65bd");
}
}
SingleLineArt.zip (33.0 KB)