Fixed and improved Brairlle Object

Added Split Camel Case extension
This commit is contained in:
LarsBrubaker 2018-03-10 16:32:04 -08:00
parent 94f6eebfe2
commit d44207ea2a
19 changed files with 274 additions and 485 deletions

View file

@ -1,187 +0,0 @@
/*
Copyright (c) 2017, Lars Brubaker, John Lewin
All rights reserved.
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 System.Globalization;
using System.Threading;
using System.Threading.Tasks;
using MatterHackers.Agg;
using MatterHackers.Agg.UI;
using MatterHackers.DataConverters3D;
using MatterHackers.Localizations;
using MatterHackers.MatterControl.PartPreviewWindow;
using MatterHackers.MeshVisualizer;
using MatterHackers.PolygonMesh;
namespace MatterHackers.MatterControl.Plugins.BrailleBuilder
{
public class BrailleEditor : IObject3DEditor
{
private MHTextEditWidget textToAddWidget;
private CheckBox useGrade2;
private BrailleGenerator brailleGenerator;
private View3DWidget view3DWidget;
private TextObject injectedItem = null;
public bool Unlocked { get; } = true;
public IEnumerable<Type> SupportedTypes() => new Type[] { typeof(TextObject) };
public GuiWidget Create(IObject3D item, View3DWidget parentView3D, ThemeConfig theme)
{
var scene = parentView3D.InteractionLayer.Scene;
injectedItem = scene?.SelectedItem as TextObject;
brailleGenerator = new BrailleGenerator();
this.view3DWidget = parentView3D;
var container = new FlowLayoutWidget(FlowDirection.TopToBottom)
{
HAnchor = HAnchor.Stretch,
};
textToAddWidget = new MHTextEditWidget("", pixelWidth: 300, messageWhenEmptyAndNotSelected: "Enter Text Here".Localize())
{
HAnchor = HAnchor.Stretch,
Margin = new BorderDouble(5),
Text = injectedItem.Text
};
textToAddWidget.ActualTextEditWidget.EnterPressed += (s, e) => RebuildText(textToAddWidget.Text);
container.AddChild(textToAddWidget);
useGrade2 = new CheckBox(new CheckBoxViewText("Use Grade 2".Localize(), textColor: ActiveTheme.Instance.PrimaryTextColor));
useGrade2.ToolTipText = "Experimental support for Braille grade 2 (contractions)".Localize();
useGrade2.Checked = false;
useGrade2.Margin = new BorderDouble(10, 5);
useGrade2.HAnchor = HAnchor.Left;
container.AddChild(useGrade2);
useGrade2.CheckedStateChanged += (sender, e) =>
{
RebuildText(textToAddWidget.Text);
};
Button updateButton = theme.ButtonFactory.Generate("Update".Localize());
updateButton.Margin = new BorderDouble(5);
updateButton.HAnchor = HAnchor.Right;
updateButton.Click += (s, e) => RebuildText(textToAddWidget.Text);
container.AddChild(updateButton);
// put in a link to the wikipedia article
{
LinkButtonFactory linkButtonFactory = new LinkButtonFactory();
linkButtonFactory.fontSize = 10;
linkButtonFactory.textColor = ActiveTheme.Instance.PrimaryTextColor;
Button moreAboutBrailleLink = linkButtonFactory.Generate("About Braille".Localize());
moreAboutBrailleLink.Margin = new BorderDouble(10, 5);
moreAboutBrailleLink.HAnchor = HAnchor.Left;
moreAboutBrailleLink.Click += (sender, e) =>
{
UiThread.RunOnIdle(() =>
{
ApplicationController.Instance.LaunchBrowser("https://en.wikipedia.org/wiki/Braille");
});
};
container.AddChild(moreAboutBrailleLink);
}
return container;
}
public string Name { get; } = "Braille";
private async void RebuildText(string brailleText)
{
injectedItem.Text = brailleText;
if (brailleText.Length <= 0)
{
return;
}
if (useGrade2.Checked)
{
brailleText = BrailleGrade2.ConvertString(brailleText);
}
brailleGenerator.ResetSettings();
var generatedItem = await Task.Run(() =>
{
Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture;
return brailleGenerator.CreateText(
brailleText,
1,
1,
injectedItem.Text);
});
var scene = view3DWidget.InteractionLayer.Scene;
scene.Children.Modify(list =>
{
// Find the injected item
var item = list.Find(child => child == injectedItem);
// Clear and refresh its children
item.Children.Modify(childList =>
{
childList.Clear();
childList.AddRange(generatedItem.Children);
});
});
}
private void RebuildBase()
{
var scene = view3DWidget.InteractionLayer.Scene;
if (scene.HasChildren() && injectedItem != null)
{
var newBaseplate = brailleGenerator.CreateBaseplate(injectedItem);
if(newBaseplate == null)
{
return;
}
// Remove the old base and create and add a new one
scene.Children.Modify(list =>
{
list.RemoveAll(child => child is BraileBasePlate);
list.Add(newBaseplate);
});
}
}
}
}

View file

@ -1,268 +0,0 @@
/*
Copyright (c) 2016, Lars Brubaker, John Lewin
All rights reserved.
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.IO;
using System.Linq;
using MatterHackers.Agg;
using MatterHackers.Agg.Font;
using MatterHackers.Agg.Platform;
using MatterHackers.Agg.VertexSource;
using MatterHackers.DataConverters3D;
using MatterHackers.DataConverters3D;
using MatterHackers.PolygonMesh;
using MatterHackers.VectorMath;
namespace MatterHackers.MatterControl.Plugins.BrailleBuilder
{
public class BrailleGenerator
{
private double lastHeightValue = 1;
private double lastSizeValue = 1;
private TypeFace brailTypeFace;
private TypeFace boldTypeFace;
private const double unscaledBaseHeight = 7;
private const double unscaledLetterHeight = 3;
private Vector2[] characterSpacing;
public BrailleGenerator()
{
boldTypeFace = TypeFace.LoadFrom(AggContext.StaticData.ReadAllText(Path.Combine("Fonts", "LiberationMono.svg")));
brailTypeFace = TypeFace.LoadFrom(AggContext.StaticData.ReadAllText(Path.Combine("Fonts", "Braille.svg")));
}
public IObject3D CreateText(string brailleText, double wordSize, double wordHeight, string wordText = null)
{
var group = new TextObject
{
Text = wordText,
ActiveEditor = "BrailleEditor"
};
TypeFacePrinter brailPrinter = new TypeFacePrinter(brailleText, new StyledTypeFace(brailTypeFace, 12));
StyledTypeFace boldStyled = new StyledTypeFace(boldTypeFace, 12);
AddCharacterMeshes(group, brailleText, brailPrinter);
Vector2 brailSize = brailPrinter.GetSize();
for (int i = 0; i < brailleText.Length; i++)
{
characterSpacing[i] += new Vector2(0, boldStyled.CapHeightInPixels * 1.5);
}
IObject3D basePlate = CreateBaseplate(group);
group.Children.Modify(list =>
{
list.Add(basePlate);
SetCharacterPositions(group);
SetWordSize(group, wordSize);
SetWordHeight(group, wordHeight);
// Remove the temporary baseplate added above and required by SetPositions/SetSize
list.Remove(basePlate);
// Add the actual baseplate that can be correctly sized to its siblings bounds
basePlate = CreateBaseplate(group);
list.Add(basePlate);
});
return group;
}
private void AddCharacterMeshes(IObject3D group, string currentText, TypeFacePrinter printer)
{
StyledTypeFace typeFace = printer.TypeFaceStyle;
characterSpacing = new Vector2[currentText.Length];
for (int i = 0; i < currentText.Length; i++)
{
string letter = currentText[i].ToString();
TypeFacePrinter letterPrinter = new TypeFacePrinter(letter, typeFace);
if (CharacterHasMesh(letterPrinter, letter))
{
#if true
Mesh textMesh = VertexSourceToMesh.Extrude(letterPrinter, unscaledLetterHeight / 2);
#else
Mesh textMesh = VertexSourceToMesh.Extrude(letterPrinter, unscaledLetterHeight / 2);
// this is the code to make rounded tops
// convert the letterPrinter to clipper polygons
List<List<IntPoint>> insetPoly = VertexSourceToPolygon.CreatePolygons(letterPrinter);
// inset them
ClipperOffset clipper = new ClipperOffset();
clipper.AddPaths(insetPoly, JoinType.jtMiter, EndType.etClosedPolygon);
List<List<IntPoint>> solution = new List<List<IntPoint>>();
clipper.Execute(solution, 5.0);
// convert them back into a vertex source
// merge both the inset and original vertex sources together
// convert the new vertex source into a mesh (triangulate them)
// offset the inner loop in z
// create the polygons from the inner loop to a center point so that there is the rest of an approximation of the bubble
// make the mesh for the bottom
// add the top and bottom together
// done
#endif
var characterObject = new Object3D()
{
Mesh = textMesh
};
characterSpacing[i] = printer.GetOffsetLeftOfCharacterIndex(i);
characterObject.Matrix *= Matrix4X4.CreateTranslation(new Vector3(0, 0, unscaledLetterHeight / 2));
group.Children.Add(characterObject);
}
//processingProgressControl.PercentComplete = ((i + 1) * 95 / currentText.Length);
}
}
private void SetCharacterPositions(IObject3D group)
{
if (group.HasChildren())
{
int i = 0;
foreach(var child in group.Children)
{
Vector3 startPosition = Vector3.Transform(Vector3.Zero, child.Matrix);
var spacing = characterSpacing[i++];
double newX = spacing.X * lastSizeValue;
double newY = spacing.Y * lastSizeValue;
child.Matrix *= Matrix4X4.CreateTranslation(new Vector3(newX, newY, startPosition.Z));
}
}
}
public void SetWordHeight(IObject3D group, double newHeight)
{
if (group.HasChildren())
{
AxisAlignedBoundingBox baseBounds = group.Children.Last().GetAxisAlignedBoundingBox(Matrix4X4.Identity);
// Skip the base item
foreach (var sceneItem in group.Children.Take(group.Children.Count - 1))
{
Vector3 startPosition = Vector3.Transform(Vector3.Zero, sceneItem.Matrix);
// take out the last scale
double oldHeight = 1.0 / lastHeightValue;
// move the part to keep it in the same relative position
sceneItem.Matrix *= Matrix4X4.CreateScale(new Vector3(1, 1, oldHeight));
sceneItem.Matrix *= Matrix4X4.CreateScale(new Vector3(1, 1, newHeight));
sceneItem.Matrix *= Matrix4X4.CreateTranslation(new Vector3(0, 0, baseBounds.ZSize - startPosition.Z));
}
lastHeightValue = newHeight;
}
}
public void SetWordSize(IObject3D group, double newSize)
{
if (group.HasChildren())
{
foreach (var object3D in group.Children)
{
Vector3 startPositionRelCenter = Vector3.Transform(Vector3.Zero, object3D.Matrix);
// take out the last scale
double oldSize = 1.0 / lastSizeValue;
Vector3 unscaledStartPositionRelCenter = startPositionRelCenter * oldSize;
Vector3 endPositionRelCenter = unscaledStartPositionRelCenter * newSize;
Vector3 deltaPosition = endPositionRelCenter - startPositionRelCenter;
// move the part to keep it in the same relative position
object3D.Matrix *= Matrix4X4.CreateScale(new Vector3(oldSize, oldSize, oldSize));
object3D.Matrix *= Matrix4X4.CreateScale(new Vector3(newSize, newSize, newSize));
object3D.Matrix *= Matrix4X4.CreateTranslation(deltaPosition);
}
lastSizeValue = newSize;
}
}
private bool CharacterHasMesh(TypeFacePrinter letterPrinter, string letter)
{
return letterPrinter.LocalBounds.Width > 0
&& letter != " "
&& letter != "\n";
}
public IObject3D CreateBaseplate(IObject3D group)
{
if (group.HasChildren())
{
AxisAlignedBoundingBox bounds = group.GetAxisAlignedBoundingBox(Matrix4X4.Identity);
double roundingScale = 20;
RectangleDouble baseRect = new RectangleDouble(bounds.minXYZ.X, bounds.minXYZ.Y, bounds.maxXYZ.X, bounds.maxXYZ.Y);
baseRect.Inflate(2);
baseRect *= roundingScale;
RoundedRect baseRoundedRect = new RoundedRect(baseRect, 1 * roundingScale);
Mesh baseMeshResult = VertexSourceToMesh.Extrude(baseRoundedRect, unscaledBaseHeight / 2 * roundingScale * lastHeightValue);
baseMeshResult.Transform(Matrix4X4.CreateScale(1 / roundingScale));
var basePlateObject = new BraileBasePlate()
{
Mesh = baseMeshResult
};
basePlateObject.Matrix *= Matrix4X4.CreateTranslation(new Vector3(0, 0, 0));
return basePlateObject;
}
return null;
}
public void ResetSettings()
{
lastHeightValue = 1;
lastSizeValue = 1;
}
}
public class BraileBasePlate : Object3D
{
}
}

View file

@ -0,0 +1,173 @@
/*
Copyright (c) 2018, Lars Brubaker, John Lewin
All rights reserved.
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.ComponentModel;
using System.IO;
using MatterHackers.Agg.Font;
using MatterHackers.Agg.Platform;
using MatterHackers.Agg.Transform;
using MatterHackers.Agg.UI;
using MatterHackers.Agg.VertexSource;
using MatterHackers.DataConverters3D;
using MatterHackers.MatterControl.DesignTools.Operations;
using MatterHackers.MatterControl.Plugins.BrailleBuilder;
using MatterHackers.VectorMath;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
namespace MatterHackers.MatterControl.DesignTools
{
[WebPageLink("About Braille", "https://en.wikipedia.org/wiki/Braille")]
public class BrailleObject3D : Object3D, IRebuildable
{
public BrailleObject3D()
{
}
public static BrailleObject3D Create()
{
var item = new BrailleObject3D();
item.Rebuild(null);
return item;
}
public override string ActiveEditor => "PublicPropertyEditor";
[DisplayName("Name")]
public string TextToEncode { get; set; } = "Braille";
public double BaseHeight { get; set; } = 5;
[Description("Use Braille grade 2 (contractions)")]
public bool UseGrade2 { get; set; }
public bool RenderAsBraille { get; set; } = true;
static TypeFace typeFace = TypeFace.LoadFrom(AggContext.StaticData.ReadAllText(Path.Combine("Fonts", "Braille.svg")));
public void Rebuild(UndoBuffer undoBuffer)
{
var aabb = this.GetAxisAlignedBoundingBox();
this.Children.Modify(list =>
{
list.Clear();
});
var brailleText = TextToEncode;
if (UseGrade2)
{
brailleText = BrailleGrade2.ConvertString(brailleText);
}
int pointSize = 28;
double pointsToMm = 0.352778;
IObject3D textObject = RenderAsBraille ? new Object3D() : this;
var offest = 0.0;
foreach (var letter in brailleText.ToCharArray())
{
IObject3D letterObject;
TypeFacePrinter letterPrinter;
if (RenderAsBraille)
{
letterPrinter = new TypeFacePrinter(letter.ToString(), new StyledTypeFace(typeFace, pointSize));
var scalledLetterPrinter = new VertexSourceApplyTransform(letterPrinter, Affine.NewScaling(pointsToMm));
// add all the spheres to letterObject
letterObject = new Object3D();
var vertexCount = 0;
var positionSum = Vector2.Zero;
var lastPosition = Vector2.Zero;
// find each dot outline and get it's center and place a sphere there
foreach (var vertex in scalledLetterPrinter.Vertices())
{
switch (vertex.command)
{
case Agg.ShapePath.FlagsAndCommand.CommandStop:
case Agg.ShapePath.FlagsAndCommand.CommandEndPoly:
case Agg.ShapePath.FlagsAndCommand.FlagClose:
case Agg.ShapePath.FlagsAndCommand.CommandMoveTo:
if(vertexCount > 0)
{
var center = positionSum / vertexCount;
double radius = (center - lastPosition).Length;
var sphere = new HalfSphereObject3D(radius * 2, 15);
sphere.Translate(center.X, center.Y);
letterObject.Children.Add(sphere);
}
vertexCount = 0;
positionSum = Vector2.Zero;
break;
case Agg.ShapePath.FlagsAndCommand.CommandCurve3:
case Agg.ShapePath.FlagsAndCommand.CommandCurve4:
case Agg.ShapePath.FlagsAndCommand.CommandLineTo:
vertexCount++;
lastPosition = vertex.position;
positionSum += lastPosition;
break;
}
}
}
else
{
letterPrinter = new TypeFacePrinter(letter.ToString(), pointSize);
var scalledLetterPrinter = new VertexSourceApplyTransform(letterPrinter, Affine.NewScaling(pointsToMm));
letterObject = new Object3D()
{
Mesh = VertexSourceToMesh.Extrude(scalledLetterPrinter, BaseHeight)
};
}
letterObject.Matrix = Matrix4X4.CreateTranslation(offest, 0, 0);
textObject.Children.Add(letterObject);
offest += letterPrinter.GetSize(letter.ToString()).X * pointsToMm;
}
if (RenderAsBraille)
{
// add the object that is the dots
this.Children.Add(textObject);
// add a plate under the dots
IObject3D basePlate = new CubeObject3D(textObject.XSize() + pointSize * pointsToMm / 2, textObject.YSize() + pointSize * pointsToMm / 2, BaseHeight);
basePlate = new SetCenter(basePlate, textObject.GetCenter() - new Vector3(0, 0, textObject.ZSize() / 2 + basePlate.ZSize() / 2));
this.Children.Add(basePlate);
}
if (aabb.ZSize > 0)
{
// If the part was already created and at a height, maintain the height.
PlatingHelper.PlaceMeshAtHeight(this, aabb.minXYZ.Z);
}
}
}
}