C3D-0311 · 260610 Tara Blvd: centre each category tag in the box it is already in done
Each tag only slides to its OWN boxs centre - no reassignment, nothing created or erased. The apparent large off-centre offsets were an attachment artifact: every tag was BottomLeft so its insertion point sat at the text corner. Sets MiddleCenter attachment, box-centre location, height 0.33, rotation along the boxs long axis, no forced wrap. Tags with no containing box keep their position and only get the height set. Fixture name labels are untouched.
C# payload
// Centre each category tag in the box it is ALREADY in. Nothing is created, erased, or moved to
// a different box - each tag only slides to its own box's centre.
//
// Why the last read looked so far "off centre": every tag is BottomLeft-attached, so its
// insertion point is the corner of the text, not its middle. On a 16.5 ft tall shelf a
// bottom-anchored vertical label legitimately reads dy = -7.98. Switching to MiddleCenter and
// placing the insertion point at the box centre is the actual centring operation.
//
// RULES:
// - only tags whose insertion point is INSIDE a minimal fixture rectangle are touched
// - tags with no box are left exactly as the user placed them
// - when two tags share one box they are offset slightly along the long axis so neither is
// hidden, still inside the box
// - height forced to 0.33; rotation follows the box's long axis
// - fixture NAME labels (chips, candy, drinks, wine, snacks, ...) are left alone; only the
// category tags are re-centred
const double H = 0.33;
string[] CATS = { "Fresh Fruits", "Fresh Uncooked Meats", "Dairy", "Canned Foods", "CANNED FOODS",
"Frozen Foods", "Dry groceries", "Non-Alcoholic", "dairy", "meats" };
var ms = (BlockTableRecord)Tx.GetObject(SymbolUtilityServices.GetBlockModelSpaceId(Db), OpenMode.ForWrite);
// ---- minimal fixture rectangles
var hseg = new List<double[]>(); var vseg = new List<double[]>();
foreach (ObjectId id in ms) {
var ln = Tx.GetObject(id, OpenMode.ForRead) as Line;
if (ln == null || !ln.Layer.Equals("FIXTURE", StringComparison.OrdinalIgnoreCase)) continue;
double x1=Math.Round(ln.StartPoint.X,3), y1=Math.Round(ln.StartPoint.Y,3);
double x2=Math.Round(ln.EndPoint.X,3), y2=Math.Round(ln.EndPoint.Y,3);
if (Math.Abs(y1-y2)<0.01) hseg.Add(new double[]{y1,Math.Min(x1,x2),Math.Max(x1,x2)});
else if (Math.Abs(x1-x2)<0.01) vseg.Add(new double[]{x1,Math.Min(y1,y2),Math.Max(y1,y2)});
}
Func<double,double,double,bool> hasH = (y,xa,xb) => { foreach (var s in hseg) if (Math.Abs(s[0]-y)<0.02 && s[1]<=xa+0.02 && s[2]>=xb-0.02) return true; return false; };
Func<double,double,double,bool> hasV = (x,ya,yb) => { foreach (var s in vseg) if (Math.Abs(s[0]-x)<0.02 && s[1]<=ya+0.02 && s[2]>=yb-0.02) return true; return false; };
var xs=new List<double>(); foreach(var s in vseg) if(!xs.Exists(v=>Math.Abs(v-s[0])<0.02)) xs.Add(s[0]);
var ys=new List<double>(); foreach(var s in hseg) if(!ys.Exists(v=>Math.Abs(v-s[0])<0.02)) ys.Add(s[0]);
xs.Sort(); ys.Sort();
var all=new List<double[]>();
for(int i=0;i<xs.Count;i++) for(int j=i+1;j<xs.Count;j++)
for(int k=0;k<ys.Count;k++) for(int l=k+1;l<ys.Count;l++){
double x0=xs[i],x1b=xs[j],y0=ys[k],y1b=ys[l];
if(x1b-x0<0.5||y1b-y0<0.5) continue;
if(!hasH(y0,x0,x1b)||!hasH(y1b,x0,x1b)||!hasV(x0,y0,y1b)||!hasV(x1b,y0,y1b)) continue;
all.Add(new double[]{x0,y0,x1b,y1b});
}
var boxes=new List<double[]>();
foreach(var b in all){
bool bigger=false;
foreach(var o in all){ if(o==b) continue;
if(o[0]>=b[0]-0.01&&o[1]>=b[1]-0.01&&o[2]<=b[2]+0.01&&o[3]<=b[3]+0.01 &&
((o[2]-o[0])*(o[3]-o[1])) < ((b[2]-b[0])*(b[3]-b[1]))-0.01){ bigger=true; break; } }
if(!bigger) boxes.Add(b);
}
Log("fixture boxes: " + boxes.Count);
// ---- which tags are in which box (by CURRENT insertion point only - no reassignment)
var perBox = new Dictionary<int, List<ObjectId>>();
var untouched = new List<ObjectId>();
foreach (ObjectId id in ms) {
var mt = Tx.GetObject(id, OpenMode.ForRead) as MText;
if (mt == null) continue;
string c = mt.Contents.Replace("\n"," ").Replace(@"\P"," ").Trim();
bool isCat = false;
foreach (var k in CATS) if (c.IndexOf(k, StringComparison.OrdinalIgnoreCase) >= 0) { isCat = true; break; }
if (!isCat) continue;
double px=mt.Location.X, py=mt.Location.Y;
int hit=-1;
for(int i=0;i<boxes.Count;i++){ var b=boxes[i];
if(px>=b[0]-0.05&&px<=b[2]+0.05&&py>=b[1]-0.05&&py<=b[3]+0.05){ hit=i; break; } }
if (hit < 0) { untouched.Add(id); continue; }
if (!perBox.ContainsKey(hit)) perBox[hit] = new List<ObjectId>();
perBox[hit].Add(id);
}
int centred = 0;
foreach (var kv in perBox) {
var b = boxes[kv.Key];
double x0=b[0], y0=b[1], x1=b[2], y1=b[3];
double bw=x1-x0, bh=y1-y0, cx=(x0+x1)/2, cy=(y0+y1)/2;
bool vertical = bh > bw;
int n = kv.Value.Count;
for (int i = 0; i < n; i++) {
var mt = (MText)Tx.GetObject(kv.Value[i], OpenMode.ForWrite);
double ox = 0, oy = 0;
if (n > 1) { // share the box: nudge along the long axis, staying inside
double span = (vertical ? bh : bw) * 0.5;
double t = (i - (n-1)/2.0) / n;
if (vertical) oy = span * t; else ox = span * t;
}
mt.TextHeight = H;
mt.Attachment = AttachmentPoint.MiddleCenter;
mt.Rotation = vertical ? Math.PI/2 : 0.0;
mt.Width = 0; // no forced wrap - let it run its natural length
mt.Location = new Point3d(cx + ox, cy + oy, 0);
centred++;
var back = (MText)Tx.GetObject(kv.Value[i], OpenMode.ForRead);
Log(string.Format(" [{0}] box{1} {2:F2}x{3:F2} -> ({4:F3},{5:F3}) rot={6:F0} \"{7}\"",
back.Handle, kv.Key, bw, bh, back.Location.X, back.Location.Y,
back.Rotation*180/Math.PI, back.Contents.Replace("\n"," ")));
}
}
foreach (var id in untouched) {
var mt = (MText)Tx.GetObject(id, OpenMode.ForWrite);
mt.TextHeight = H; // height only; position left as the user set it
var back = (MText)Tx.GetObject(id, OpenMode.ForRead);
Log(string.Format(" [{0}] no box - left at ({1:F2},{2:F2}), h={3:F2} \"{4}\"",
back.Handle, back.Location.X, back.Location.Y, H, back.Contents.Replace("\n"," ")));
}
Log("");
Log(string.Format("centred {0} tags in their own boxes, {1} left in place (height set to {2:F2})",
centred, untouched.Count, H));
Ed.Regen();
Result
Log
fixture boxes: 19 [21BE7] box9 1.50x16.50 -> (2261079.677,1391833.943) rot=90 "Dry groceries and baked goods" [21BE8] box11 1.50x16.50 -> (2261081.177,1391833.943) rot=90 "Dry groceries and baked goods" [21BE9] box8 4.00x1.00 -> (2261080.427,1391842.693) rot=0 "Dry groceries and\P baked goods" [21BEC] box18 1.50x8.50 -> (2261089.177,1391837.943) rot=90 "Non-Alcoholic beverages" [21BED] box15 1.50x16.50 -> (2261087.677,1391833.943) rot=90 "Dry groceries and baked goods" [21BEF] box4 3.00x2.00 -> (2261072.427,1391824.693) rot=0 "Dry groceries and\Pbaked goods" [21BF0] box10 3.00x2.00 -> (2261080.427,1391824.693) rot=0 "Fresh Fruits &\P Vegetables" [21BF4] box7 2.50x3.00 -> (2261074.177,1391847.693) rot=90 "Frozen Foods" [21C06] box5 1.50x12.50 -> (2261073.177,1391831.943) rot=90 "CANNED FOODS" [21C99] box16 3.00x2.00 -> (2261088.427,1391824.693) rot=0 "Dry groceries and\Pbaked goods" [21CD6] box0 4.00x3.00 -> (2261067.427,1391816.693) rot=0 "dairy" [21CDE] box2 3.00x3.00 -> (2261070.927,1391816.693) rot=0 "meats" [21CED] box6 1.50x4.00 -> (2261073.177,1391840.193) rot=90 "Dry groceries \Pand baked goods" [21CFB] box14 4.00x1.00 -> (2261088.427,1391842.693) rot=0 "Dry groceries and\P baked goods" [21D30] box12 4.00x3.00 -> (2261085.427,1391847.693) rot=0 "Dry groceries and\P baked goods" [21D40] box13 4.00x2.00 -> (2261085.427,1391850.193) rot=0 "Non-Alcoholic beverages" [21BEA] no box - left at (2261070.43,1391842.19), h=0.33 "Dry groceries and\P baked goods" [21BF2] no box - left at (2261065.13,1391818.59), h=0.33 "Frozen Foods" [21BF6] no box - left at (2261097.73,1391845.04), h=0.33 "Non-Alcoholic beverages" [21CB9] no box - left at (2261063.73,1391838.39), h=0.33 "Frozen Foods" [21CC1] no box - left at (2261065.13,1391825.44), h=0.33 "dairy" centred 16 tags in their own boxes, 5 left in place (height set to 0.33)
Notes
What worked, what didn't, job-specific gotchas — flagged notes feed the recipes.
No notes yet.