Here’s my translated code.
It is working, but there are some issues with the centerPoint. It’s not the centerPoint! On a single Node it’s the bottomLeft Point if I don’t press Ctrl. With Ctrl it’s the real centerPoint (which in my opinion is right).
Even with two selected nodes (one left, one right with same ycoord) it’s not the centerPoint of the boundingRect it’s some point on the bottomLine of the right node. With ctrl pressed it’s the real centerPoint of each node (which is also right).
public class CustomRotatingTool : RotatingTool
{
private Point centerPoint;
private double initalAngle;
private Dictionary<Part, PartInfo> initialInfo;
public override void DoActivate()
{
base.DoActivate();
// center point of the collection
centerPoint = Diagram.Panel.ComputeBounds(Diagram.SelectedParts).Center();
// remember the angle realtive to the center point when rotating the whole collection
initalAngle = centerPoint.DirectionPoint(Diagram.LastMousePointInModel);
// remember initial angle and distance for each part
var infos = new Dictionary<Part, PartInfo>();
Diagram.SelectedParts.ForEach(p => WalkTree(p, infos));
initialInfo = infos;
}
private void WalkTree(Part part, Dictionary<Part, PartInfo> infos)
{
if (part == null || part is Link) return;
// distance from centerPoint to locationSpot of part
var node = part as Node;
if (node == null) return;
var dist = centerPoint.DistancePoint(node.Location);
// calculate initial relative angle
var dir = centerPoint.DirectionPoint(node.Location);
// saves part-angle combination in dirtionary
infos.Add(part, new PartInfo(dir, dist, node.RotationAngle));
// recurse into groups - Achtung! Die Group wurde oben nicht eingetütet
var group = part as Group;
if (group == null) return;
using (var it = group.MemberNodes.GetEnumerator())
{
while (it.MoveNext())
{
WalkTree(it.Current, infos);
}
}
}
/// <summary>
/// Override Rotate to rotate all selected objects about their collective center.
/// When the control key is held down while rotating, all selected objects are rotated individually.
/// </summary>
/// <param name="newangle"></param>
protected override void DoRotate(double newangle)
{
Debug.WriteLine($"{centerPoint} {OriginalAngle} {newangle}");
// when rotating individual parts, remember the original angle difference
var angleDiff = newangle - AdornedNode.RotationAngle;
var tool = this;
initialInfo.ForEach(kvp =>
{
var part = kvp.Key;
if (part is Link) return; // only nodes and simple parts
var partInfo = kvp.Value;
// rotate every selected non-link part
// find information about the part set in initialInfo
if ((Keyboard.Modifiers & ModifierKeys.Control) > 0) // control-key pressed
{
if (Equals(tool.AdornedNode, part))
{
tool.AdornedNode.RotationAngle = newangle;
}
else
{
var node = part as Node;
if (node != null)
node.RotationAngle += angleDiff;
}
}
else
{
var radAngle = newangle*(Math.PI/180); // converts the angle traveled from degrees to radians
// calculate the part's x-y location relative to the central rotation point
var offsetX = partInfo.Distance*Math.Cos(radAngle + partInfo.PlacementAngle);
var offsetY = partInfo.Distance*Math.Sin(radAngle + partInfo.PlacementAngle);
var node = part as Node;
if (node != null)
{
// move part
node.Location = new Point(tool.centerPoint.X + offsetX, tool.centerPoint.Y + offsetY);
// rotate part
node.RotationAngle = partInfo.RotationAngle + newangle;
}
}
}
);
//base.DoRotate(newangle);
}
/// <summary>
/// This override needs to calculate the desired angle with different rotation points,
/// depanding on wheter we are rotating the whole selection as one, or Parts individually.
/// </summary>
/// <param name="newPoint"></param>
/// <returns></returns>
protected override double ComputeRotate(Point newPoint)
{
double angle;
if ((Keyboard.Modifiers & ModifierKeys.Control) > 0 || Diagram.SelectedParts.Count == 1)
{
// relative to the center of the Node whose handle we are rotating
var part = AdornedNode;
var rotationPoint = part.Location;
angle = rotationPoint.DirectionPoint(newPoint);
}
else
{
// relative to the center of the whole selection
angle = centerPoint.DirectionPoint(newPoint) - initalAngle;
}
if (angle >= 360) angle -= 360;
else if (angle < 0) angle += 360;
var interval = Math.Min(Math.Abs(SnapAngleMultiple), 180);
var epsilon = Math.Min(Math.Abs(SnapAngleEpsilon), interval / 2);
// if it's close to a multiple of INTERVAL degrees, make it exactly so
if ((Keyboard.Modifiers & ModifierKeys.Shift) == 0 && interval > 0 && epsilon > 0)
{
if (angle % interval < epsilon)
{
angle = Math.Floor(angle / interval) * interval;
}
else if (angle % interval > interval - epsilon)
{
angle = (Math.Floor(angle / interval) + 1) * interval;
}
if (angle >= 360) angle -= 360;
else if (angle < 0) angle += 360;
}
return angle;
}
}
public class PartInfo
{
public double PlacementAngle;
public double Distance;
public double RotationAngle;
public PartInfo(double placementAngle, double distance, double rotationAngle)
{
PlacementAngle = placementAngle * (Math.PI / 180); // in radians
Distance = distance;
RotationAngle = rotationAngle; // in degrees
}
}
public static class PointExtensions
{
public static double DirectionPoint(this Point pointUr, Point point)
{
Vector v1 = new Vector(point.X, point.Y);
Vector v2 = new Vector(pointUr.X, pointUr.Y);
return Vector.AngleBetween(new Vector(1, 0), v1 - v2);
}
public static double DistancePoint(this Point pointUr, Point point)
{
Vector v1 = new Vector(point.X, point.Y);
Vector v2 = new Vector(pointUr.X, pointUr.Y);
return (v1 - v2).Length;
}
}`