The about page now renders most of its content
This commit is contained in:
parent
3e5b966fd1
commit
157b68545e
5 changed files with 298 additions and 197 deletions
|
|
@ -36,11 +36,15 @@ using MatterHackers.Localizations;
|
|||
using MatterHackers.MatterControl.ContactForm;
|
||||
using MatterHackers.MatterControl.CustomWidgets;
|
||||
using MatterHackers.MatterControl.DataStorage;
|
||||
using MatterHackers.MatterControl.HtmlParsing;
|
||||
|
||||
namespace MatterHackers.MatterControl
|
||||
{
|
||||
public class AboutPage : GuiWidget
|
||||
{
|
||||
static string htmlContent = null;
|
||||
|
||||
GuiWidget htmlWidget;
|
||||
LinkButtonFactory linkButtonFactory = new LinkButtonFactory();
|
||||
TextImageButtonFactory textImageButtonFactory = new TextImageButtonFactory();
|
||||
RGBA_Bytes aboutTextColor = ActiveTheme.Instance.PrimaryTextColor;
|
||||
|
|
@ -68,24 +72,91 @@ namespace MatterHackers.MatterControl
|
|||
customInfoTopToBottom.AddChild(new UpdateControl());
|
||||
//AddMatterHackersInfo(customInfoTopToBottom);
|
||||
|
||||
WidgetFromHtml creator = new WidgetFromHtml();
|
||||
HtmlParser htmlParser = new HtmlParser();
|
||||
|
||||
creator.AddMapping("translate", DoTranslate);
|
||||
creator.AddMapping("toUpper", DoToUpper);
|
||||
creator.AddMapping("versionNumber", GetVersionString);
|
||||
creator.AddMapping("buildNumber", GetBuildString);
|
||||
creator.AddMapping("linkButton", CreateLinkButton);
|
||||
creator.AddMapping("centeredButton", CreateCenteredButton);
|
||||
if (htmlContent == null)
|
||||
{
|
||||
string aboutHtmlFile = Path.Combine(ApplicationDataStorage.Instance.ApplicationStaticDataPath, "OEMSettings", "AboutPage.html");
|
||||
htmlContent = File.ReadAllText(aboutHtmlFile);
|
||||
}
|
||||
|
||||
htmlWidget = new FlowLayoutWidget(FlowDirection.TopToBottom);
|
||||
htmlWidget.VAnchor = VAnchor.Max_FitToChildren_ParentHeight;
|
||||
htmlWidget.HAnchor |= HAnchor.ParentCenter;
|
||||
|
||||
string aboutHtmlFile = Path.Combine(ApplicationDataStorage.Instance.ApplicationStaticDataPath, "OEMSettings", "AboutPage.html");
|
||||
string htmlContent = File.ReadAllText(aboutHtmlFile);
|
||||
GuiWidget htmlWidget = creator.CreateWidget(htmlContent);
|
||||
htmlParser.ParseHtml(htmlContent, AddContent, CloseContent);
|
||||
|
||||
customInfoTopToBottom.AddChild(htmlWidget);
|
||||
|
||||
this.AddChild(customInfoTopToBottom);
|
||||
}
|
||||
|
||||
FlowLayoutWidget currentRow;
|
||||
private void AddContent(HtmlParser htmlParser, string htmlContent)
|
||||
{
|
||||
ElementState elementState = htmlParser.CurrentElementState;
|
||||
switch (elementState.TypeName)
|
||||
{
|
||||
case "a":
|
||||
break;
|
||||
|
||||
case "table":
|
||||
break;
|
||||
|
||||
case "td":
|
||||
case "span":
|
||||
GuiWidget widgetToAdd;
|
||||
|
||||
TextWidget content = new TextWidget(htmlContent, pointSize: elementState.PointSize, textColor: ActiveTheme.Instance.PrimaryTextColor);
|
||||
widgetToAdd = content;
|
||||
|
||||
currentRow.AddChild(widgetToAdd);
|
||||
break;
|
||||
|
||||
case "tr":
|
||||
currentRow = new FlowLayoutWidget();
|
||||
if (elementState.HeightPercent == 100)
|
||||
{
|
||||
currentRow.VAnchor = VAnchor.ParentBottomTop;
|
||||
}
|
||||
if (elementState.Alignment == ElementState.AlignType.center)
|
||||
{
|
||||
currentRow.HAnchor |= HAnchor.ParentCenter;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
private void CloseContent(HtmlParser htmlParser, string htmlContent)
|
||||
{
|
||||
ElementState elementState = htmlParser.CurrentElementState;
|
||||
switch (elementState.TypeName)
|
||||
{
|
||||
case "a":
|
||||
break;
|
||||
|
||||
case "table":
|
||||
break;
|
||||
|
||||
case "span":
|
||||
break;
|
||||
|
||||
case "tr":
|
||||
htmlWidget.AddChild(currentRow);
|
||||
currentRow = null;
|
||||
break;
|
||||
|
||||
case "td":
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
public string DoTranslate(string content)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
|
|
|
|||
213
AboutPage/HtmlParser.cs
Normal file
213
AboutPage/HtmlParser.cs
Normal file
|
|
@ -0,0 +1,213 @@
|
|||
/*
|
||||
Copyright (c) 2014, Lars Brubaker
|
||||
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.Linq;
|
||||
using System.Text;
|
||||
using System.IO;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
using MatterHackers.Agg;
|
||||
using MatterHackers.Agg.UI;
|
||||
|
||||
namespace MatterHackers.MatterControl.HtmlParsing
|
||||
{
|
||||
public class ElementState
|
||||
{
|
||||
public enum AlignType { none, center };
|
||||
public enum VerticalAlignType { none, top };
|
||||
|
||||
internal List<string> classes = new List<string>();
|
||||
public List<string> Classes { get { return classes; } }
|
||||
internal string typeName;
|
||||
public string TypeName { get { return typeName; } }
|
||||
|
||||
internal string href;
|
||||
public string Href { get { return href; } }
|
||||
|
||||
internal string id;
|
||||
public string Id { get { return id; } }
|
||||
|
||||
internal AlignType alignment;
|
||||
public AlignType Alignment { get { return alignment; } }
|
||||
internal VerticalAlignType verticalAlignment;
|
||||
public VerticalAlignType VerticalAlignment { get { return verticalAlignment; } }
|
||||
|
||||
internal double pointSize = 12;
|
||||
public double PointSize { get { return pointSize; } }
|
||||
internal int heightPercent = 0;
|
||||
public int HeightPercent { get { return heightPercent; } }
|
||||
|
||||
internal ElementState()
|
||||
{
|
||||
}
|
||||
|
||||
internal ElementState(ElementState copy)
|
||||
{
|
||||
alignment = copy.alignment;
|
||||
verticalAlignment = copy.verticalAlignment;
|
||||
pointSize = copy.pointSize;
|
||||
// not part of the ongoing state
|
||||
//heightPercent = copy.heightPercent;
|
||||
}
|
||||
}
|
||||
|
||||
public class HtmlParser
|
||||
{
|
||||
Stack<ElementState> elementQueue = new Stack<ElementState>();
|
||||
public ElementState CurrentElementState { get { return elementQueue.Peek(); } }
|
||||
|
||||
public delegate void ProcessContent(HtmlParser htmlParser, string content);
|
||||
|
||||
const string typeNameEnd = @"[ >]";
|
||||
private static readonly Regex typeNameEndRegex = new Regex(typeNameEnd, RegexOptions.Compiled);
|
||||
public void ParseHtml(string htmlContent, ProcessContent addContentFunction, ProcessContent closeContentFunction)
|
||||
{
|
||||
elementQueue.Push(new ElementState());
|
||||
|
||||
int currentPosition = 0;
|
||||
while (currentPosition < htmlContent.Length)
|
||||
{
|
||||
int openPosition = htmlContent.IndexOf('<', currentPosition);
|
||||
if (openPosition == -1)
|
||||
{
|
||||
break;
|
||||
}
|
||||
int closePosition = htmlContent.IndexOf('>', openPosition);
|
||||
if (htmlContent[openPosition + 1] == '/')
|
||||
{
|
||||
closeContentFunction(this, null);
|
||||
elementQueue.Pop();
|
||||
|
||||
// get any content that is after this close but before the next open or close
|
||||
int nextOpenPosition = htmlContent.IndexOf('<', closePosition);
|
||||
if (nextOpenPosition > closePosition + 1)
|
||||
{
|
||||
string contentBetweenInsideAndEnd = htmlContent.Substring(closePosition + 1, nextOpenPosition - (closePosition + 1));
|
||||
addContentFunction(this, contentBetweenInsideAndEnd);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ParesTypeContent(openPosition, closePosition, htmlContent);
|
||||
|
||||
int endOfName = typeNameEndRegex.Match(htmlContent, openPosition + 1).Index;
|
||||
elementQueue.Peek().typeName = htmlContent.Substring(openPosition + 1, endOfName - (openPosition + 1));
|
||||
|
||||
int nextOpenPosition = htmlContent.IndexOf('<', closePosition);
|
||||
string content = htmlContent.Substring(closePosition + 1, nextOpenPosition - closePosition - 1);
|
||||
addContentFunction(this, content);
|
||||
}
|
||||
currentPosition = closePosition + 1;
|
||||
}
|
||||
}
|
||||
|
||||
private void ParesTypeContent(int openPosition, int closePosition, string htmlContent)
|
||||
{
|
||||
string text = htmlContent.Substring(openPosition, closePosition - openPosition);
|
||||
ElementState style = new ElementState(elementQueue.Peek());
|
||||
int afterTypeName = typeNameEndRegex.Match(htmlContent, openPosition).Index;
|
||||
if (afterTypeName < closePosition)
|
||||
{
|
||||
string content = htmlContent.Substring(afterTypeName, closePosition - afterTypeName).Trim();
|
||||
string[] splitOnSpace = new Regex("' ").Split(content);
|
||||
for (int i = 0; i < splitOnSpace.Length; i++)
|
||||
{
|
||||
string[] splitOnEquals = new Regex("='").Split(splitOnSpace[i]);
|
||||
switch (splitOnEquals[0])
|
||||
{
|
||||
case "style":
|
||||
ParseStyleContent(splitOnEquals[1].Substring(0, splitOnEquals[1].Length - 1), style);
|
||||
break;
|
||||
|
||||
case "align":
|
||||
break;
|
||||
|
||||
case "class":
|
||||
{
|
||||
string[] classes = splitOnEquals[1].Split(' ');
|
||||
foreach (string className in classes)
|
||||
{
|
||||
style.classes.Add(className);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case "href":
|
||||
style.href = splitOnEquals[1].Substring(0, splitOnEquals[1].Length - 1);
|
||||
break;
|
||||
|
||||
case "id":
|
||||
style.id = splitOnEquals[1].Substring(0, splitOnEquals[1].Length - 1);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
elementQueue.Push(style);
|
||||
}
|
||||
|
||||
private void ParseStyleContent(string styleContent, ElementState style)
|
||||
{
|
||||
string[] splitOnSemi = styleContent.Split(';');
|
||||
for (int i = 0; i < splitOnSemi.Length; i++)
|
||||
{
|
||||
if (splitOnSemi[i].Length > 0)
|
||||
{
|
||||
string[] splitOnColon = splitOnSemi[i].Split(':');
|
||||
switch (splitOnColon[0])
|
||||
{
|
||||
case "height":
|
||||
style.heightPercent = int.Parse(splitOnColon[1].Substring(0, splitOnColon[1].Length - 1));
|
||||
break;
|
||||
|
||||
case "text-align":
|
||||
style.alignment = (ElementState.AlignType)Enum.Parse(typeof(ElementState.AlignType), splitOnColon[1]);
|
||||
break;
|
||||
|
||||
case "font-size":
|
||||
style.pointSize = int.Parse(splitOnColon[1].Substring(0, splitOnColon[1].Length - 2));
|
||||
break;
|
||||
|
||||
case "vertical-align":
|
||||
style.verticalAlignment = (ElementState.VerticalAlignType)Enum.Parse(typeof(ElementState.VerticalAlignType), splitOnColon[1]);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,183 +0,0 @@
|
|||
/*
|
||||
Copyright (c) 2014, Lars Brubaker
|
||||
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.Linq;
|
||||
using System.Text;
|
||||
using System.IO;
|
||||
|
||||
using MatterHackers.Agg;
|
||||
using MatterHackers.Agg.UI;
|
||||
|
||||
namespace MatterHackers.MatterControl
|
||||
{
|
||||
public class WidgetFromHtml
|
||||
{
|
||||
internal class StyleState
|
||||
{
|
||||
internal enum AlignType { none, center };
|
||||
internal enum VerticalAlignType { none, top };
|
||||
|
||||
internal AlignType alignment;
|
||||
internal VerticalAlignType vertiaclAlignment;
|
||||
|
||||
internal double pointSize = 12;
|
||||
internal int heightPercent = 0;
|
||||
|
||||
internal StyleState()
|
||||
{
|
||||
}
|
||||
|
||||
internal StyleState(StyleState copy)
|
||||
{
|
||||
alignment = copy.alignment;
|
||||
vertiaclAlignment = copy.vertiaclAlignment;
|
||||
pointSize = copy.pointSize;
|
||||
heightPercent = copy.heightPercent;
|
||||
}
|
||||
}
|
||||
|
||||
Stack<StyleState> styleQueue = new Stack<StyleState>();
|
||||
|
||||
public delegate string ProcessContent(string content);
|
||||
|
||||
class ClassToFunctionMapping
|
||||
{
|
||||
string className;
|
||||
ProcessContent function;
|
||||
|
||||
public ClassToFunctionMapping(string className, ProcessContent function)
|
||||
{
|
||||
this.className = className;
|
||||
this.function = function;
|
||||
}
|
||||
}
|
||||
|
||||
List<WidgetFromHtml.ClassToFunctionMapping> classFunctionMapping = new List<WidgetFromHtml.ClassToFunctionMapping>();
|
||||
|
||||
public void AddMapping(string className, ProcessContent function)
|
||||
{
|
||||
classFunctionMapping.Add(new ClassToFunctionMapping(className, function));
|
||||
}
|
||||
|
||||
GuiWidget widgetBeingCreated;
|
||||
public GuiWidget CreateWidget(string htmlContent)
|
||||
{
|
||||
styleQueue.Push(new StyleState());
|
||||
widgetBeingCreated = new FlowLayoutWidget(FlowDirection.TopToBottom);
|
||||
|
||||
int currentPosition = 0;
|
||||
while (currentPosition < htmlContent.Length)
|
||||
{
|
||||
int openPosition = htmlContent.IndexOf('<', currentPosition);
|
||||
if (openPosition == -1)
|
||||
{
|
||||
break;
|
||||
}
|
||||
int closePosition = htmlContent.IndexOf('>', openPosition);
|
||||
if (htmlContent[openPosition + 1] == '/')
|
||||
{
|
||||
styleQueue.Pop();
|
||||
}
|
||||
else
|
||||
{
|
||||
ParesTypeContent(openPosition, closePosition, htmlContent);
|
||||
|
||||
if(htmlContent.Substring(openPosition+1).StartsWith("span"))
|
||||
{
|
||||
int nextOpenPosition = htmlContent.IndexOf('<', closePosition);
|
||||
AddContent(htmlContent.Substring(closePosition, nextOpenPosition - closePosition));
|
||||
}
|
||||
}
|
||||
currentPosition = closePosition + 1;
|
||||
}
|
||||
|
||||
return widgetBeingCreated;
|
||||
}
|
||||
|
||||
private void AddContent(string htmlContent)
|
||||
{
|
||||
widgetBeingCreated.AddChild(new TextWidget(htmlContent));
|
||||
}
|
||||
|
||||
private void ParesTypeContent(int openPosition, int closePosition, string htmlContent)
|
||||
{
|
||||
string text = htmlContent.Substring(openPosition, closePosition - openPosition);
|
||||
StyleState style = new StyleState(styleQueue.Peek());
|
||||
int afterType = htmlContent.IndexOf(' ', openPosition);
|
||||
if (afterType < closePosition)
|
||||
{
|
||||
string content = htmlContent.Substring(afterType, closePosition - afterType).Trim();
|
||||
string[] splitOnEquals = content.Split('=');
|
||||
switch (splitOnEquals[0])
|
||||
{
|
||||
case "style":
|
||||
ParseStyleContent(splitOnEquals[1].Substring(1, splitOnEquals[1].Length - 2), style);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
styleQueue.Push(style);
|
||||
}
|
||||
|
||||
private void ParseStyleContent(string styleContent, StyleState style)
|
||||
{
|
||||
string[] splitOnSemi = styleContent.Split(';');
|
||||
for (int i = 0; i < splitOnSemi.Length; i++)
|
||||
{
|
||||
if (splitOnSemi[i].Length > 0)
|
||||
{
|
||||
string[] splitOnColon = splitOnSemi[i].Split(':');
|
||||
switch (splitOnColon[0])
|
||||
{
|
||||
case "height":
|
||||
style.heightPercent = int.Parse(splitOnColon[1].Substring(0, splitOnColon[1].Length - 1));
|
||||
break;
|
||||
|
||||
case "text-align":
|
||||
style.alignment = (StyleState.AlignType)Enum.Parse(typeof(StyleState.AlignType), splitOnColon[1]);
|
||||
break;
|
||||
|
||||
case "font-size":
|
||||
style.pointSize = int.Parse(splitOnColon[1].Substring(0, splitOnColon[1].Length - 2));
|
||||
break;
|
||||
|
||||
case "vertical-align":
|
||||
style.vertiaclAlignment = (StyleState.VerticalAlignType)Enum.Parse(typeof(StyleState.VerticalAlignType), splitOnColon[1]);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -82,7 +82,7 @@
|
|||
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
|
||||
<ItemGroup>
|
||||
<Compile Include="AboutPage\UpdateControl.cs" />
|
||||
<Compile Include="AboutPage\WidgetFromHtml.cs" />
|
||||
<Compile Include="AboutPage\HtmlParser.cs" />
|
||||
<Compile Include="ActionBar\ActionBarBaseControls.cs" />
|
||||
<Compile Include="ActionBar\ActionBarPlus.cs" />
|
||||
<Compile Include="ActionBar\TemperatureWidgetBed.cs" />
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<table style='height:100%;text-align:center;font-size:12pt'>
|
||||
<table align='center' style='height:100%;text-align:center;font-size:12pt'>
|
||||
<tr><td><span style='font-size:20pt'>MatterControl</span><span style='vertical-align:top;font-size:10pt'>™</span></td></tr>
|
||||
<tr><td style='font-size:10pt'><span class='translate'>Version </span><span class='versionNumber'>0.0.0</span></td></tr>
|
||||
<tr><td style='font-size:10pt'><span class='toUpper translate'>Developed by:</span><span style='font-size:14pt'> MatterHackers</span></td></tr>
|
||||
|
|
@ -6,8 +6,8 @@
|
|||
<tr><td>Please consider <a href='http://www.matterhackers.com/store/printer-accessories/mattercontrol-donation'>donating</a> to help support MatterControl.</td></tr>
|
||||
<tr style='height:100%;'><td> </td></tr>
|
||||
<tr><td>Special thanks to:</td></tr>
|
||||
<tr><td>Alessandro Ranellucci for <a href="https://github.com/alexrj/Slic3r">Slic3r</a></td></tr>
|
||||
<tr><td>David Braam and Ultimaker BV for <a href="https://github.com/Ultimaker/CuraEngine">CuraEngine</a></td></tr>
|
||||
<tr><td>Alessandro Ranellucci for <a href='https://github.com/alexrj/Slic3r'>Slic3r</a></td></tr>
|
||||
<tr><td>David Braam and Ultimaker BV for <a href='https://github.com/Ultimaker/CuraEngine'>CuraEngine</a></td></tr>
|
||||
<tr style='height:100%;'><td> </td></tr>
|
||||
<tr><td class='centeredButton translate'>Send FeedBack</td></tr>
|
||||
<tr><td><a href='http://www.matterhackers.com/'>www.matterhackers.com</a></td></tr>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue