Merge pull request #4676 from larsbrubaker/2.19.7

2.19.7
This commit is contained in:
Lars Brubaker 2019-11-12 10:23:47 -08:00 committed by GitHub
commit bdd68a876b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 600 additions and 35 deletions

View file

@ -132,28 +132,6 @@ namespace MatterHackers.MatterControl.DesignTools.Operations
return objectToTranslate;
}
private static VertexStorage CombinePaths(IVertexSource a, IVertexSource b, ClipType clipType)
{
List<List<IntPoint>> aPolys = a.CreatePolygons();
List<List<IntPoint>> bPolys = b.CreatePolygons();
Clipper clipper = new Clipper();
clipper.AddPaths(aPolys, PolyType.ptSubject, true);
clipper.AddPaths(bPolys, PolyType.ptClip, true);
List<List<IntPoint>> intersectedPolys = new List<List<IntPoint>>();
clipper.Execute(clipType, intersectedPolys);
Clipper.CleanPolygons(intersectedPolys);
VertexStorage output = intersectedPolys.CreateVertexStorage();
output.Add(0, 0, ShapePath.FlagsAndCommand.Stop);
return output;
}
public static Affine GetCenteringTransformExpandedToRadius(this IVertexSource vertexSource, double radius)
{
var circle = SmallestEnclosingCircle.MakeCircle(vertexSource.Vertices().Select((v) => new Vector2(v.position.X, v.position.Y)));
@ -281,16 +259,6 @@ namespace MatterHackers.MatterControl.DesignTools.Operations
};
}
public static IVertexSource Minus(this IVertexSource a, IVertexSource b)
{
return CombinePaths(a, b, ClipType.ctDifference);
}
public static IVertexSource Plus(this IVertexSource a, IVertexSource b)
{
return CombinePaths(a, b, ClipType.ctUnion);
}
public static double XSize(this IObject3D item)
{
return item.GetAxisAlignedBoundingBox().XSize;

View file

@ -243,6 +243,12 @@ namespace MatterHackers.MatterControl.DesignTools.Operations
}
// and select this
var rootItem = this.Ancestors().Where(i => scene.Children.Contains(i)).FirstOrDefault();
if (rootItem != null)
{
scene.SelectedItem = rootItem;
}
scene.SelectedItem = this;
this.Invalidate(InvalidateType.Children);

View file

@ -99,6 +99,7 @@ namespace MatterHackers.MatterControl.DesignTools.Operations
list.AddRange(ItemWithTransform.Children);
});
}
Invalidate(InvalidateType.Children);
}

View file

@ -0,0 +1,580 @@
/*
Involute Spur Gear Builder (c) 2014 Dr. Rainer Hessmer
ported to C# 2019 by Lars Brubaker
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
The views and conclusions contained in the software and documentation are those
of the authors and should not be interpreted as representing official policies,
either expressed or implied, of the FreeBSD Project.
*/
using System;
using System.Collections.Generic;
using ClipperLib;
using MatterHackers.Agg;
using MatterHackers.Agg.Transform;
using MatterHackers.Agg.VertexSource;
using MatterHackers.DataConverters2D;
using MatterHackers.VectorMath;
namespace MatterHackers.MatterControl.DesignTools
{
// Involute Spur Gear Builder
// For calculating and drawing involute spur gears.
// As an improvement over the majority of other freely available scripts and utilities it fully accounts for undercuts.
// For additional information please head over to:
// http://www.hessmer.org/blog/2014/01/01/online-involute-spur-gear-builder part 1
// http://www.hessmer.org/blog/2015/07/13/online-involute-spur-gear-builder-part-2/ part 2
// The implementation is inspired by the subtractive process that Michal Zalewski's describes in
// http://lcamtuf.coredump.cx/gcnc/ch6/#6.2 part six of his excellent
// http://lcamtuf.coredump.cx/gcnc/ Guerrilla guide to CNC machining, mold making, and resin casting
public class Gear2D : VertexSourceLegacySupport
{
private double _circularPitch = 8;
/// <summary>
/// Gets or sets distance from one face of a tooth to the corresponding face of an adjacent tooth on the same gear, measured along the pitch circle.
/// </summary>
public double CircularPitch
{
get => _circularPitch;
set
{
_circularPitch = value;
CalculateDependants();
}
}
private double diametralPitch;
private double addendum;
private double clearance = .05;
// Most common stock gears have a 20° pressure angle, with 14½° and 25° pressure angle gears being much less
// common. Increasing the pressure angle increases the width of the base of the gear tooth, leading to greater strength and load carrying capacity. Decreasing
// the pressure angle provides lower backlash, smoother operation and less sensitivity to manufacturing errors. (reference: http://en.wikipedia.org/wiki/Involute_gear)
private double pressureAngle = 20;
private double backlash = .05;
private double profileShift = 0;
private double shiftedAddendum;
private double outerRadius;
private double angleToothToTooth;
private int _toothCount = 30;
public int ToothCount
{
get => _toothCount;
set
{
_toothCount = value;
CalculateDependants();
}
}
private GearType gearType = GearType.External;
private int stepsPerToothAngle = 10;
private double pitchDiameter;
private double pitchRadius;
private Vector2 center = Vector2.Zero;
private Gear2D connectedGear;
private double centerHoleDiameter = 4;
public enum GearType
{
External,
Internal,
Rack
}
public Gear2D()
{
CalculateDependants();
}
private void CalculateDependants()
{
// convert circular pitch to diametral pitch
this.diametralPitch = Math.PI / this.CircularPitch; // Ratio of the number of teeth to the pitch diameter
// this.circularPitch = Math.PI / this.diametralPitch;
this.center = Vector2.Zero; // center of the gear
// this.angle = 0; // angle in degrees of the complete gear (changes during rotation animation)
// Pitch diameter: Diameter of pitch circle.
this.pitchDiameter = this.ToothCount / this.diametralPitch;
this.pitchRadius = this.pitchDiameter / 2;
// Addendum: Radial distance from pitch circle to outside circle.
this.addendum = 1 / this.diametralPitch;
// Typically no profile shift is used meaning that this.shiftedAddendum = this.addendum
this.shiftedAddendum = this.addendum * (1 + this.profileShift);
// Outer Circle
this.outerRadius = this.pitchRadius + this.shiftedAddendum;
this.angleToothToTooth = 360.0 / this.ToothCount;
}
public override IEnumerable<VertexData> Vertices()
{
IVertexSource shape;
// shape = CreateRackShape();
shape = CreateRegularGearShape();
// shape = CreateInternalGearShape();
// shape = CreateSingleTooth().tooth;
// shape = CreateInternalGearShape();
return shape.Vertices();
}
private IVertexSource CreateRackShape()
{
IVertexSource rack = new VertexStorage();
// we draw one tooth in the middle and then five on either side
for (var i = 0; i < ToothCount; i++)
{
var tooth = this.CreateRackTooth();
tooth = tooth.Translate(0, (0.5 + -ToothCount / 2 + i) * this.CircularPitch);
rack = rack.Union(tooth);
}
// creating the bar backing the teeth
var rightX = -(this.addendum + this.clearance);
var width = 4 * this.addendum;
var halfHeight = ToothCount * this.CircularPitch / 2;
var bar = new RoundedRect(rightX - width, -halfHeight, rightX, halfHeight, 0);
var rackFinal = rack.Union(bar) as VertexStorage;
rackFinal.Translate(this.addendum * this.profileShift, 0);
return rackFinal;
}
private IVertexSource CreateRegularGearShape()
{
var tooth = this.CreateSingleTooth();
// we could now take the tooth cutout, rotate it tooth count times and union the various slices together into a complete gear.
// However, the union operations become more and more complex as the complete gear is built up.
// So instead we capture the outer path of the tooth and concatenate rotated versions of this path into a complete outer gear path.
// Concatenating paths is inexpensive resulting in significantly faster execution.
var outlinePaths = tooth.tooth;
// first we need to find the corner that sits at the center
for (var i = 1; i < this.ToothCount; i++)
{
var angle = i * this.angleToothToTooth;
var roatationMatrix = Affine.NewRotation(MathHelper.DegreesToRadians(angle));
var rotatedCorner = new VertexSourceApplyTransform(tooth.tooth, roatationMatrix);
outlinePaths = new CombinePaths(outlinePaths, rotatedCorner);
}
var gearShape = tooth.wheel.Subtract(outlinePaths);
if (this.centerHoleDiameter > 0)
{
var radius = this.centerHoleDiameter / 2;
var centerhole = new Ellipse(0, 0, radius, radius)
{
ResolutionScale = 10
};
gearShape = gearShape.Subtract(centerhole) as VertexStorage;
}
return gearShape;// .RotateZDegrees(-90);
}
private IVertexSource CreateInternalGearShape()
{
var singleTooth = this._createInternalToothProfile();
// return singleTooth;
var outlinePaths = singleTooth;
var corners = outlinePaths as VertexStorage;
// first we need to find the corner that sits at the center
var centerCornerIndex = 0;
var radius = this.pitchRadius + (1 + this.profileShift) * this.addendum + this.clearance;
var delta = 0.0000001;
for (var i = 0; i < corners.Count; i++)
{
var corner = corners[i];
if (corner.Y < delta && (corner.X + radius) < delta)
{
centerCornerIndex = i;
break;
}
}
var outerCorners = new VertexStorage();
for (var i = 2; i < corners.Count - 2; i++)
{
var corner = corners[(i + centerCornerIndex) % corners.Count];
outerCorners.add(corner.position);
}
var reversedOuterCorners = new VertexStorage();
foreach (var vertex in new ReversePath(outerCorners).Vertices())
{
reversedOuterCorners.add(vertex.position);
}
outerCorners = reversedOuterCorners;
var cornersCount = outerCorners.Count;
for (var i = 1; i < this.ToothCount; i++)
{
var angle = i * this.angleToothToTooth;
var roatationMatrix = Affine.NewRotation(MathHelper.DegreesToRadians(angle));
for (var j = 0; j < cornersCount; j++)
{
var rotatedCorner = roatationMatrix.Transform(outerCorners[j].position);
outerCorners.add(rotatedCorner);
}
}
outerCorners = this._smoothConcaveCorners(outerCorners) as VertexStorage;
var innerRadius = this.pitchRadius + (1 - this.profileShift) * this.addendum + this.clearance;
var outerRadius = innerRadius + 4 * this.addendum;
var outerCircle = new Ellipse(this.center, outerRadius, outerRadius);
// return outerCircle;
// return gearCutout;
return outerCircle.Subtract(outerCorners);
}
private IVertexSource CreateRackTooth()
{
var toothWidth = this.CircularPitch / 2;
var toothDepth = this.addendum + this.clearance;
var sinPressureAngle = Math.Sin(this.pressureAngle * Math.PI / 180);
var cosPressureAngle = Math.Cos(this.pressureAngle * Math.PI / 180);
// if a positive backlash is defined then we widen the trapezoid accordingly.
// Each side of the tooth needs to widened by a fourth of the backlash (vertical to cutter faces).
var dx = this.backlash / 4 / cosPressureAngle;
var leftDepth = this.addendum + this.clearance;
var upperLeftCorner = new Vector2(-leftDepth, toothWidth / 2 - dx + (this.addendum + this.clearance) * sinPressureAngle);
var upperRightCorner = new Vector2(this.addendum, toothWidth / 2 - dx - this.addendum * sinPressureAngle);
var lowerRightCorner = new Vector2(upperRightCorner[0], -upperRightCorner[1]);
var lowerLeftCorner = new Vector2(upperLeftCorner[0], -upperLeftCorner[1]);
var tooth = new VertexStorage();
tooth.MoveTo(upperLeftCorner);
tooth.LineTo(upperRightCorner);
tooth.LineTo(lowerRightCorner);
tooth.LineTo(lowerLeftCorner);
return tooth;
}
private (IVertexSource tooth, IVertexSource wheel) CreateSingleTooth()
{
// create outer circle sector covering one tooth
var toothSectorPath = new Arc(Vector2.Zero, new Vector2(this.outerRadius, this.outerRadius), MathHelper.DegreesToRadians(90), MathHelper.DegreesToRadians(90 - this.angleToothToTooth))
{
ResolutionScale = 10
};
var toothCutOut = CreateToothCutout();
return (toothCutOut, toothSectorPath);
}
private IVertexSource CreateToothCutout()
{
var angleToothToTooth = 360.0 / this.ToothCount;
var angleStepSize = this.angleToothToTooth / this.stepsPerToothAngle;
IVertexSource toothCutout = new VertexStorage();
var toothCutterShape = this.CreateToothCutter();
var bounds = toothCutterShape.GetBounds();
var lowerLeftCorner = new Vector2(bounds.Left, bounds.Bottom);
// To create the tooth profile we move the (virtual) infinite gear and then turn the resulting cutter position back.
// For illustration see http://lcamtuf.coredump.cx/gcnc/ch6/, section 'Putting it all together'
// We continue until the moved tooth cutter's lower left corner is outside of the outer circle of the gear.
// Going any further will no longer influence the shape of the tooth
var stepCounter = 0;
while (true)
{
var angle = stepCounter * angleStepSize;
var xTranslation = new Vector2(angle * Math.PI / 180 * this.pitchRadius, 0);
var movedLowerLeftCorner = lowerLeftCorner + xTranslation;
movedLowerLeftCorner = Vector2.Rotate(movedLowerLeftCorner, MathHelper.DegreesToRadians(angle));
if (movedLowerLeftCorner.Length > this.outerRadius)
{
// the cutter is now completely outside the gear and no longer influences the shape of the gear tooth
break;
}
// we move in both directions
var movedToothCutterShape = toothCutterShape.Translate(xTranslation);
movedToothCutterShape = movedToothCutterShape.RotateZDegrees(angle);
toothCutout = toothCutout.Union(movedToothCutterShape);
if (xTranslation[0] > 0)
{
movedToothCutterShape = toothCutterShape.Translate(new Vector2(-xTranslation[0], xTranslation[1]));
movedToothCutterShape = movedToothCutterShape.RotateZDegrees(-angle);
toothCutout = toothCutout.Union(movedToothCutterShape);
}
stepCounter++;
}
toothCutout = this._smoothConcaveCorners(toothCutout);
return toothCutout.RotateZDegrees(-this.angleToothToTooth / 2);
}
private IVertexSource CreateToothCutter()
{
// we create a trapezoidal cutter as described at http://lcamtuf.coredump.cx/gcnc/ch6/ under the section 'Putting it all together'
var toothWidth = this.CircularPitch / 2;
var cutterDepth = this.addendum + this.clearance;
var cutterOutsideLength = 3 * this.addendum;
var sinPressureAngle = Math.Sin(this.pressureAngle * Math.PI / 180.0);
var cosPressureAngle = Math.Cos(this.pressureAngle * Math.PI / 180.0);
// if a positive backlash is defined then we widen the trapezoid accordingly.
// Each side of the tooth needs to widened by a fourth of the backlash (vertical to cutter faces).
var dx = this.backlash / 2 / cosPressureAngle;
var lowerRightCorner = new Vector2(toothWidth / 2 + dx - cutterDepth * sinPressureAngle, this.pitchRadius + this.profileShift * this.addendum - cutterDepth);
var upperRightCorner = new Vector2(toothWidth / 2 + dx + cutterOutsideLength * sinPressureAngle, this.pitchRadius + this.profileShift * this.addendum + cutterOutsideLength);
var upperLeftCorner = new Vector2(-upperRightCorner[0], upperRightCorner[1]);
var lowerLeftCorner = new Vector2(-lowerRightCorner[0], lowerRightCorner[1]);
var cutterPath = new VertexStorage();
cutterPath.MoveTo(lowerLeftCorner);
cutterPath.LineTo(upperLeftCorner);
cutterPath.LineTo(upperRightCorner);
cutterPath.LineTo(lowerRightCorner);
return cutterPath;
}
private IVertexSource _smoothConvexCorners(IVertexSource corners)
{
// removes single convex corners located between concave corners
return this._smoothCorners(corners, true); // removeSingleConvex
}
private IVertexSource _createInternalToothCutter()
{
// To cut the internal gear teeth, the actual pinion comes close but we need to enlarge it so properly caters for clearance and backlash
var pinion = this.connectedGear;
var enlargedPinion = new Gear2D()
{
CircularPitch = pinion.CircularPitch,
// pressureAngle: pinion.pressureAngle,
// clearance: -pinion.clearance,
// backlash: -pinion.backlash,
// toothCount: pinion.toothCount,
// centerHoleDiameter: 0,
// profileShift: pinion.profileShift,
// stepsPerToothAngle: pinion.stepsPerToothAngle
};
var tooth = enlargedPinion.CreateSingleTooth();
return tooth.tooth.RotateZDegrees(90 + 180 / enlargedPinion.ToothCount); // we need a tooth pointing to the left
}
private IVertexSource _createInternalToothProfile()
{
var radius = this.pitchRadius + (1 - this.profileShift) * this.addendum + this.clearance;
var angleToothToTooth = 360 / this.ToothCount;
var sin = Math.Sin(angleToothToTooth / 2 * Math.PI / 180);
var cos = Math.Cos(angleToothToTooth / 2 * Math.PI / 180);
var fullSector = new VertexStorage();
fullSector.MoveTo(0, 0);
fullSector.LineTo(-(radius * cos), radius * sin);
fullSector.LineTo(-radius, 0);
fullSector.LineTo(-(radius * cos), -radius * sin);
var innerRadius = radius - (2 * this.addendum + this.clearance);
var innerCircle = new Ellipse(this.center, innerRadius)
{
ResolutionScale = 10
};
var sector = fullSector.Subtract(innerCircle);
var cutterTemplate = this._createInternalToothCutter();
var pinion = this.connectedGear;
var stepsPerTooth = this.stepsPerToothAngle;
var angleStepSize = angleToothToTooth / stepsPerTooth;
var toothShape = sector;
var cutter = cutterTemplate.Translate(-this.pitchRadius + this.connectedGear.pitchRadius, 0);
toothShape = toothShape.Subtract(cutter);
for (var i = 1; i < stepsPerTooth; i++)
{
var pinionRotationAngle = i * angleStepSize;
var pinionCenterRayAngle = -pinionRotationAngle * pinion.ToothCount / this.ToothCount;
// var cutter = cutterTemplate;
cutter = cutterTemplate.RotateZDegrees(pinionRotationAngle);
cutter = cutter.Translate(-this.pitchRadius + this.connectedGear.pitchRadius, 0);
cutter = cutter.RotateZDegrees(pinionCenterRayAngle);
toothShape = toothShape.Subtract(cutter);
cutter = cutterTemplate.RotateZDegrees(-pinionRotationAngle);
cutter = cutter.Translate(-this.pitchRadius + this.connectedGear.pitchRadius, 0);
cutter = cutter.RotateZDegrees(-pinionCenterRayAngle);
toothShape = toothShape.Subtract(cutter);
}
return toothShape;
}
private IVertexSource _smoothConcaveCorners(IVertexSource corners)
{
// removes single concave corners located between convex corners
return this._smoothCorners(corners, false); // removeSingleConvex
}
private IVertexSource _smoothCorners(IVertexSource corners_in, bool removeSingleConvex)
{
var corners = corners_in as VertexStorage;
var isConvex = new List<bool>();
var previousCorner = corners[corners.Count - 1];
var currentCorner = corners[0];
for (var i = 0; i < corners.Count; i++)
{
var nextCorner = corners[(i + 1) % corners.Count];
var v1 = previousCorner.position - currentCorner.position;
var v2 = nextCorner.position - currentCorner.position;
var crossProduct = v1.Cross(v2);
isConvex.Add(crossProduct < 0);
previousCorner = currentCorner;
currentCorner = nextCorner;
}
// we want to remove any concave corners that are located between two convex corners
var cleanedUpCorners = new VertexStorage();
var previousIndex = corners.Count - 1;
var currentIndex = 0;
for (var i = 0; i < corners.Count; i++)
{
var corner = corners[currentIndex];
var nextIndex = (i + 1) % corners.Count;
var isSingleConcave = !isConvex[currentIndex] && isConvex[previousIndex] && isConvex[nextIndex];
var isSingleConvex = isConvex[currentIndex] && !isConvex[previousIndex] && !isConvex[nextIndex];
previousIndex = currentIndex;
currentIndex = nextIndex;
if (removeSingleConvex && isSingleConvex)
{
continue;
}
if (!removeSingleConvex && isSingleConcave)
{
continue;
}
cleanedUpCorners.Add(corner.X, corner.Y, corner.command);
}
return cleanedUpCorners;
}
}
}
public static class Extensions
{
public static IVertexSource Subtract(this IVertexSource a, IVertexSource b)
{
return a.Minus(b);
}
public static IVertexSource Union(this IVertexSource a, IVertexSource b)
{
return a.Plus(b);
}
public static IVertexSource RotateZDegrees(this IVertexSource a, double angle)
{
return new VertexSourceApplyTransform(a, Affine.NewRotation(MathHelper.DegreesToRadians(angle)));
}
public static IVertexSource Translate(this IVertexSource a, Vector2 delta)
{
return new VertexSourceApplyTransform(a, Affine.NewTranslation(delta));
}
public static IVertexSource Minus(this IVertexSource a, IVertexSource b)
{
return CombinePaths(a, b, ClipType.ctDifference);
}
public static IVertexSource Plus(this IVertexSource a, IVertexSource b)
{
return CombinePaths(a, b, ClipType.ctUnion);
}
private static VertexStorage CombinePaths(IVertexSource a, IVertexSource b, ClipType clipType)
{
List<List<IntPoint>> aPolys = a.CreatePolygons();
List<List<IntPoint>> bPolys = b.CreatePolygons();
var clipper = new Clipper();
clipper.AddPaths(aPolys, PolyType.ptSubject, true);
clipper.AddPaths(bPolys, PolyType.ptClip, true);
var outputPolys = new List<List<IntPoint>>();
clipper.Execute(clipType, outputPolys);
Clipper.CleanPolygons(outputPolys);
VertexStorage output = outputPolys.CreateVertexStorage();
output.Add(0, 0, ShapePath.FlagsAndCommand.Stop);
return output;
}
}

View file

@ -85,6 +85,7 @@ namespace MatterHackers.MatterControl.DesignTools
{
newContainer.Children.Add(child.Clone());
}
undoBuffer.AddAndDo(new ReplaceCommand(new[] { this }, new[] { newContainer }));
}

View file

@ -38,7 +38,6 @@ using MatterHackers.Agg.Platform;
using MatterHackers.Agg.UI;
using MatterHackers.DataConverters3D;
using MatterHackers.Localizations;
using MatterHackers.MatterControl.CustomWidgets;
using MatterHackers.MatterControl.Library;
using MatterHackers.MatterControl.PartPreviewWindow.PlusTab;
using MatterHackers.MatterControl.PrintLibrary;

View file

@ -1620,6 +1620,12 @@ namespace MatterHackers.MatterControl.PartPreviewWindow
private void Scene_Invalidated(object sender, InvalidateArgs e)
{
if (Scene.Descendants().Count() != lastSceneDescendantsCount)
{
rebuildTreePending = true;
UiThread.RunOnIdle(this.RebuildTree);
}
if (e.InvalidateType.HasFlag(InvalidateType.Children)
&& !rebuildTreePending)
{
@ -1636,10 +1642,13 @@ namespace MatterHackers.MatterControl.PartPreviewWindow
rebuildTreePending = true;
UiThread.RunOnIdle(this.RebuildTree);
}
Scene.SelectedItem = null;
Scene.SelectedItem = lastSelectedItem;
}
lastSceneDescendantsCount = Scene.Descendants().Count();
// Invalidate widget on scene invalidate
this.Invalidate();
}
@ -1704,6 +1713,7 @@ namespace MatterHackers.MatterControl.PartPreviewWindow
private bool assigningTreeNode;
private FlowLayoutWidget treeNodeContainer;
private int lastSceneDescendantsCount;
private InlineStringEdit workspaceName;
public InteractiveScene Scene => sceneContext.Scene;

@ -1 +1 @@
Subproject commit 09299400c077dcf7ee494dd95823daaa7c156bd9
Subproject commit 8551764ee5116ad0d479442bfcba36385bc16594

@ -1 +1 @@
Subproject commit 494c44d1184e4cf30223bc8fa30348d287462349
Subproject commit 24360177b57b10f7d3cdc6dd97e57e76e8f95ade