![v2-8d8e77ad8374211e0c474002c9ef2fed_1440w.jpg?source=172ae18b](http://img-01.proxy.5ce.com/view/image?&type=2&guid=831cfd4a-452f-eb11-8da9-e4434bdf6706&url=https://pic1.zhimg.com/v2-8d8e77ad8374211e0c474002c9ef2fed_1440w.jpg?source=172ae18b)
无聊之中发现SolidWorks2019提供了glTF格式的转换,可是下载完2019才发现只有订阅用户才能使用此功能。苦闷过后,便想到作为一个SolidWorks开发者,为SolidWorks开发一个转glTF的插件。同时,也为其他格式的转换提供一个思路。
![v2-c9ca917f6b5724f00f7e7047129507d6_b.jpg](http://img-01.proxy.5ce.com/view/image?&type=2&guid=831cfd4a-452f-eb11-8da9-e4434bdf6706&url=https://pic3.zhimg.com/v2-c9ca917f6b5724f00f7e7047129507d6_b.jpg)
1. 什么是glTF
1) glTF(GL TransmissionFormat),即图形语言交换格式,它是一种3D内容的格式标准,由Khronos Group管理(Khronos Group还管理着OpenGL系列、OpenCL等重要的行业标准);
2) glTF的设计是面向实时渲染应用的,尽量提供可以直接传输给图形API的数据形式,不再需要二次转换;
3) glTF对OpenGL ES、WebGL非常友好;
4) glTF的目标是:3D领域的JPEG;
5) 作为一个标准,自2015年10月发布(glTF 1.0)以来,已经得到了业界广泛的认可,你可以相信它的水平;
6) glTF目前最新版本为2.0已于2017年6月正式发布。
简单来说,就是3D领域的JPEG SolidWorks对gltf的介绍如下。
SOLIDWORKS的“Publish to Xtended Reality”选项将允许用户将CAD模型从SOLIDWORKS导出至开源格式的定制版本,即“glTF”。一旦完成,用户就可以通过Meta 2开发套件头显的Model Viewer平台浏览CAD模型。
2. 相关介绍
KhronosGroup/glTFgithub.com![v2-143fec8f0f9e23987e6b40f3ba3e9720_ipico.jpg](http://img-01.proxy.5ce.com/view/image?&type=2&guid=831cfd4a-452f-eb11-8da9-e4434bdf6706&url=https://pic1.zhimg.com/v2-143fec8f0f9e23987e6b40f3ba3e9720_ipico.jpg)
• 英文文档
• 中文翻译 --翻译的很好
二.搭建一个Taskpane的SolidWorks插件
虽然可以在另存为选项中扩展gltf功能,但为了在SolidWorks界面上显而易见,我选择了添加一个TaskPane界面。
![v2-0e9ac0f276e64ab3c6fbebdcddee9ba2_b.jpg](http://img-03.proxy.5ce.com/view/image?&type=2&guid=831cfd4a-452f-eb11-8da9-e4434bdf6706&url=https://pic3.zhimg.com/v2-0e9ac0f276e64ab3c6fbebdcddee9ba2_b.jpg)
。
2.新建一个带TaskPane的SolidWork插件
使用SolidWors自带SwCSharpAddin模板建立一个SolidWorks插件,关于如何找到这个模板可以在看我的另一篇文章。 ###### 2.1 新建一个名为DuSwToglTF的插件
2.2 去掉和按钮有关的代码
2.3 添加一个名为Convert.xaml的WPF用户控件.注意添加关于WPF的类库引用
System.xaml;WindowsBase;WindowsFormsIntegration;PresentationCore;PresentionFramework;
• 将UI Methods区域代码删除,并添加AddTaskPanel()方法
#region UI Methods
private void AddTaskPanel()
{
try
{
//DuSwToglTF.ConvertPanel.PreloadDlls();
ITaskpaneView pTaskPanView;
pTaskPanView = iSwApp.CreateTaskpaneView2((ConvertPanel.RootPath+""+ "gltf.bmp"), "将Solidworks文件转换为glTF");
if (TaskPanelControl == null)
{
SwAddin.TaskPanelControl = new ConvertPanel(SwApp);
eleHost.Child = TaskPanelControl;
}
pTaskPanView.DisplayWindowFromHandlex64(eleHost.Handle.ToInt64());
}
catch (Exception ex)
{
SwApp.SendMsgToUser(ex.ToString());
}
}
#endregion
这段代码首先定义了一个TaskpaneView对象,然后通过ISldworks创建了一个TaskpaneView对象。紧接着实例化了一个WPF的用户控件, 此处使用的一个ElementHost来作为WPF界面的容器。此方面的介绍可以查看Winform与WPF的互操作性。最后调用DisplayWindowFromHandlex64方法显示了这个WPF控件,ToInt64()方法传递了一个指针给SolidWorks以便SolidWorks显示。
2.4 在ConnectToSW方法中调用AddTaskPanel()
public bool ConnectToSW(object ThisSW, int cookie)
{
iSwApp = (ISldWorks)ThisSW;
addinID = cookie;
//Setup callbacks
iSwApp.SetAddinCallbackInfo(0, this, addinID);
#region 添加Taskpane
AddTaskPanel();
#endregion
#region Setup the Event Handlers
SwEventPtr = (SolidWorks.Interop.sldworks.SldWorks)iSwApp;
openDocs = new Hashtable();
AttachEventHandlers();
#endregion
return true;
}
3.使用WPF做一个转换界面
接下来我们需要设计一下UI来实现我们的功能 ###### 3.1 安装必要nuget库
• MVVMLight来实现MVVM模式
• HandyControl 来美化UI
使用HandyControl注意在xaml文件中引用命名空间
xmlns:hc="https://handyorg.github.io/handycontrol"
并添加资源字典
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/HandyControl;component/Themes/SkinDefault.xaml"/>
<ResourceDictionary Source="pack://application:,,,/HandyControl;component/Themes/Theme.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
3.2 添加类 ConvertPanelViewModel.cs
namespace DuSwToglTF
{
public class ConvertPanelViewModel:GalaSoft.MvvmLight.ViewModelBase
{
}
}
3.3 设计界面--完整的xaml文件如下
<UserControl x:Class="DuSwToglTF.ConvertPanel"
xmlns=http://schemas.microsoft.com/winfx/2006/xaml/presentation
xmlns:x=http://schemas.microsoft.com/winfx/2006/xaml
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:DuSwToglTF"
mc:Ignorable="d"
xmlns:hc=https://handyorg.github.io/handycontrol
TextElement.FontWeight="Medium"
TextElement.FontSize="10"
FontFamily="pack://application:,,,/MaterialDesignThemes.Wpf;component/Resources/Roboto/#Roboto"
d:DesignHeight="600" d:DesignWidth="300">
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/HandyControl;component/Themes/SkinDefault.xaml"/>
<ResourceDictionary Source="pack://application:,,,/HandyControl;component/Themes/Theme.xaml"/>
</ResourceDictionary.MergedDictionaries>
</UserControl.Resources>
<TabControl>
<TabItem>
<TabItem.Header>
<Image Source="pack://application:,,,/DuSwToglTF;component/Resources/Main.png" Width="16" Height="16"></Image>
</TabItem.Header>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="340"/>
<RowDefinition Height="16"/>
<RowDefinition/>
</Grid.RowDefinitions>
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" Margin="0,0,-0.4,0">
<StackPanel Grid.Row="0" Margin="0" Grid.RowSpan="2">
<GroupBox Margin="0,10" ToolTip="Save Path">
<GroupBox.Header >
<Label>保存路径</Label>
</GroupBox.Header>
<Grid Margin="0,10">
<Grid.RowDefinitions>
<RowDefinition Height="43*"/>
<RowDefinition Height="50"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="79*"/>
<ColumnDefinition Width="21*"/>
</Grid.ColumnDefinitions>
<TextBox Text="{Binding FileName}"></TextBox>
<Label Grid.Row="0" Grid.Column="1" ToolTip="FileName">文件名</Label>
<TextBox Text="{Binding FilePath}" Grid.Column="0" Margin="2,10" Grid.Row="1"/>
<Button Grid.Column="1" Command="{Binding ChoosePathCommand}" Content="..." Margin="0,10" Height="30" ToolTip="选择保存路径" Grid.Row="1"/>
</Grid>
</GroupBox>
<Button Margin="10,2" Command="{Binding SaveCommand}" Click="Button_Click">
保存(Save)
</Button>
<!--<StackPanel Orientation="Horizontal">
<ProgressBar HorizontalAlignment="Stretch" Width="295"></ProgressBar>
</StackPanel>-->
<Grid Margin="2">
<hc:Card Header="选项">
<StackPanel>
<StackPanel Margin="0,5" Orientation="Horizontal">
<CheckBox HorizontalAlignment="Left" Margin="10,0" IsChecked="{Binding IsOpenFile}"
ToolTip="保存完成后打开文件(Open gltf file after saved)">
保存完打开
</CheckBox>
<CheckBox HorizontalAlignment="Left" Margin="10,0" IsChecked="{Binding IsOpenFolder}"
ToolTip="保存完成后打开文件夹(Open folder after saved)">
保存完成后打开文件夹
</CheckBox>
</StackPanel>
<Slider Margin="0,10" VerticalAlignment="Center"
Minimum="0"
Maximum="100"
ToolTip="网格精度(待实现)"
Value="50"
/>
</StackPanel>
</hc:Card>
</Grid>
<GroupBox Header="批量转换" Visibility="Collapsed">
</GroupBox>
</StackPanel>
</ScrollViewer>
<GridSplitter Grid.Row="1" Background="AliceBlue" BorderBrush="Black" BorderThickness="1" HorizontalAlignment="Stretch" Margin="0,7,-0.4,7" />
<Grid Grid.Row="2" Margin="0,0,-0.4,0">
<Grid.RowDefinitions>
<RowDefinition Height="14*"/>
<RowDefinition/>
</Grid.RowDefinitions>
<!--<materialDesign:Card>
<GroupBox Header="批量转换(开发中)" Margin="5,10">
<StackPanel>
</StackPanel>
</GroupBox>
</materialDesign:Card>-->
</Grid>
</TabItem>
<TabItem>
<TabItem.Header>
<Image Source="pack://application:,,,/DuSwToglTF;component/Resources/About.png" Width="16" Height="16"></Image>
</TabItem.Header>
<StackPanel>
<Grid Margin="5">
<GroupBox >
<GroupBox.Header>
<Label ToolTip="Info">信息</Label>
</GroupBox.Header>
<StackPanel>
<TextBlock HorizontalAlignment="Center" FontSize="15" Margin="5">
DuSwToglTF V0.0.1 Preview
</TextBlock>
<TextBlock Margin="4">1.支持格式:sldprt,sldasm转换为glTF文件</TextBlock>
<TextBlock Margin="4" TextWrapping="Wrap">2.对多实体零件或者装配体的输出未进行测试,可能产生错误</TextBlock>
<TextBlock Margin="4">2.包含几何信息和颜色信息,单一面的颜色信息暂未考虑</TextBlock>
<TextBlock Margin="4">3.暂未支持光照度,场景,动画等功能</TextBlock>
</StackPanel>
</GroupBox>
</Grid>
<Grid Margin="5" >
<Expander Header="NuGet">
<!--<Expander.Header >
<Label ToolTip="使用的库或框架">使用的库或框架
</Label>
</Expander.Header>-->
<StackPanel>
<Label>1.HandyControl</Label>
<Label>2.MVVM-MVVMLight</Label>
<Label>2.glTF-sharpglTF</Label>
</StackPanel>
</Expander>
</Grid>
<Grid Margin="5" >
<GroupBox Header="项目信息">
<StackPanel>
<TextBlock>
<Hyperlink></Hyperlink>
</TextBlock>
<TextBlock Margin="5">Code by DuDuDu</TextBlock>
<TextBlock Margin="5">Contact me:1831197727@qq.com</TextBlock>
<StackPanel Orientation="Horizontal">
<Label>Project:</Label>
<TextBlock VerticalAlignment="Center">
<Hyperlink>https://github.com/weianweigan/DuSwToglTF</Hyperlink>
</TextBlock>
</StackPanel>
</StackPanel>
</GroupBox>
</Grid>
</StackPanel>
</TabItem>
</TabControl>
3.4将相应属性绑定到viewmodel上
详细的代码可以去GitHub查看
3.5 关于实时获取当前打开的文档,以便自动填写文件路径和名字。
为了在打开零件或者装配体时自动填写文件名和当前打开的文件路径,我们需要监听切换文档的事件。
• 在SwAddin.cs类的OnDocChange()方法中设置文档名字和路径
public int OnDocChange()
{
TaskPanelControl.viewmodel.SetModelDoc();
return 0;
}
3.6 实现ConvertPanelViewModel中的SaveClick()方法
/// <summary>
/// 保存按钮执行的动作
/// </summary>
private void SaveClick()
{
List<string> files = null;
Controller.Convertor.ErrorType errors = Controller.Convertor.ErrorType.NoErros;
IsInProgress = true;
if (!System.IO.Directory.Exists(FilePath))
{
swApp.SendMsgToUser("当前路径不存在:" + FilePath);
return;
}
if (FilePath.Contains("-") || FileName.Contains("-"))
{
swApp.SendMsgToUser("-为非法字符,路径或者文件民包含-字符,请修改路径或者文件名后重新保存");
return;
}
try
{
//会堵塞UI;TODO:异步方式实现转换
var model = Controller.Convertor.DuConvertor.ConvertToglTFModel(swModel, out errors);
if (model != null)
{
files = Controller.Convertor.DuConvertor.SaveAs(model, FilePath, FileName);
}
swApp.SendMsgToUser("保存完成");
if (files != null && IsOpenFile && files.Count >= 3)
{
System.Diagnostics.Process.Start( files[2]);
}
if (IsOpenFolder)
{
System.Diagnostics.Process.Start("explorer.exe", FilePath);
}
IsInProgress = false;
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
// System.Diagnostics.Process.Start("ExpLore", "C:window");
}
*下面将介绍对SolidWorks模型处理和保存。
var model = Controller.Convertor.DuConvertor.ConvertToglTFModel(swModel, out errors);
if (model != null)
{
files = Controller.Convertor.DuConvertor.SaveAs(model, FilePath, FileName);
}
三.获取SolidWorks文件中的网格信息
1.关于格式转换
SolidWorks作为一个参数化设计软件,其格式信息中肯定包含了许多特征信息。但gltf作为一个多数情况下只用来浏览的格式,我们需要的只是几何,材质,动画等信息。其实我们能从SolidWorks中解析出来的出了特征以外大概也就是那么多信息。
2.网格(Mesh)
• 从SoidWorks实体中提取出我们需要的网格信息,然后将其转换到gltf的Mesh节点。
2.1 提取实体信息
• 假设我们拿到了一个*.sldprt文件。在转换时我们可以通过打开此文件,或者获取sldworks中的活动文档来获取到一个ModelDoc2接口。
ModelDoc2 swModel = swApp.ActiveDoc;
SolidWorks作为一个多是体建模软件,所以一个零件中可能存在多个实体(Body,注意与Entity的区别都翻译为实体)。如果有多个实体,我们就要获取全部实体。
private SWglTFModel ConvertPartToglTF(ModelDoc2 swModel)
{
SWglTFModel Model = new SWglTFModel();
var MaterialValue = ((PartDoc)swModel).MaterialPropertyValues;
if (MaterialValue != null)
{
Model.PartMaterialValue = MaterialValue;
}
object[] bodys = ((PartDoc)swModel).GetBodies((int)swBodyType_e.swAllBodies);
foreach (Body2 swBody in bodys)
{
Model.BodyList.Add(GetglTFBodyModel(swBody));
}
return Model;
}
获取每个实体中的网格材质
private Model.BodyglTFModel GetglTFBodyModel(Body2 swBody)
{
BodyglTFModel BodyModel = new BodyglTFModel();
if (swBody == null)
{
return null;
}
try
{
var BodyMaterial = (double[])swBody.MaterialPropertyValues2;
if (BodyMaterial != null)
{
BodyModel.BodyMaterialValue = BodyMaterial;
}
#region 网格化
Tessellation swTessellation = swBody.GetTessellation(null);
if (swTessellation != null)
{
swTessellation.NeedFaceFacetMap = true;
swTessellation.NeedVertexParams = true;
swTessellation.NeedVertexNormal = true;
swTessellation.ImprovedQuality = true;
// How to handle matches across common edges
swTessellation.MatchType = (int)swTesselationMatchType_e.swTesselationMatchFacetTopology;
// Do it
bool bResult = swTessellation.Tessellate();
}
else
{
return null;
}
#endregion
Face2 swFace = (Face2)swBody.GetFirstFace();
while (swFace != null)
{
Model.FaceglTFModel FaceModel = new FaceglTFModel();
var FaceMaterial = swFace.MaterialPropertyValues;
if (FaceMaterial != null)
{
FaceModel.FaceMaterialValue = FaceMaterial;
}
#region 面的三角化
int[] aFacetIds = (int[])swTessellation.GetFaceFacets(swFace);
int iNumFacetIds = aFacetIds.Length;
for (int iFacetIdIdx = 0; iFacetIdIdx < iNumFacetIds; iFacetIdIdx++)
{
int[] aFinIds = (int[])swTessellation.GetFacetFins(aFacetIds[iFacetIdIdx]);
// There should always be three fins per facet
FaceVertexModel model = new FaceVertexModel();
List<double[]> points = new List<double[]>();
for (int iFinIdx = 0; iFinIdx < 3; iFinIdx++)
{
int[] aVertexIds = (int[])swTessellation.GetFinVertices(aFinIds[iFinIdx]);
// Should always be two vertices per fin
double[] aVertexCoords1 = (double[])swTessellation.GetVertexPoint(aVertexIds[0]);
double[] aVertexCoords2 = (double[])swTessellation.GetVertexPoint(aVertexIds[1]);
var v1 = new VERTEX(Convert.ToSingle(aVertexCoords1[0]),
Convert.ToSingle(aVertexCoords1[1]),
Convert.ToSingle(aVertexCoords1[2]));
var v2 = new VERTEX(Convert.ToSingle(aVertexCoords2[0]),
Convert.ToSingle(aVertexCoords2[1]),
Convert.ToSingle(aVertexCoords2[2]));
bool isContain = false;
foreach (var item in points)
{
if ((Math.Abs(item[0] - aVertexCoords1[0]) + Math.Abs(item[1] - aVertexCoords1[1]) + Math.Abs(item[2] - aVertexCoords1[2])) < 0.00001)
{
isContain = true;
}
}
if (!isContain)
{
points.Add(aVertexCoords1);
}
isContain = false;
foreach (var item in points)
{
if ((Math.Abs(item[0] - aVertexCoords2[0]) + Math.Abs(item[1] - aVertexCoords2[1]) + Math.Abs(item[2] - aVertexCoords2[2])) < 0.00001)
{
isContain = true;
}
}
if (!isContain)
{
points.Add(aVertexCoords2);
}
}
if (points.Count == 3)
{
model.a = new VERTEX(Convert.ToSingle(points[0][0]),
Convert.ToSingle(points[0][1]),
Convert.ToSingle(points[0][2]));
model.b = new VERTEX(Convert.ToSingle(points[1][0]),
Convert.ToSingle(points[1][1]),
Convert.ToSingle(points[1][2]));
model.c = new VERTEX(Convert.ToSingle(points[2][0]),
Convert.ToSingle(points[2][1]),
Convert.ToSingle(points[2][2]));
FaceModel.FaceTri.Add(model);
}
}
if (FaceModel.FaceTri.Count > 0)
{
BodyModel.FaceList.Add(FaceModel);
}
swFace = (Face2)swFace.GetNextFace();
#endregion
}
}
catch (Exception ex)
{
}
return BodyModel;
}
2.3 关于 SWglTFModel BodyglTFModel FaceglTFModel 和 FaceVertexModel
为了完成整个装欢过程,我将SolidWorks中的信息转换到SWglTFModel类中,SWglTFModel中描述了一个模型所有的材质信息,他具有的所有实体,以及这个实体的材质和实体所有的所有面,每一个面又包含了组成他的、 所有顶点信息(FaceVertexModel)和材质。在完成了ModelDoc2向SWglTFModel这个数据结构的转换后,再由这个类完成向gltf的转换,整个转换过程类似一个适配器模式。
• 下一节将介绍这几个类。
四.获取SolidWorks中的材质信息
4.1 关于材质
其实在上一节的代码里已经有获取SolidWorks的材质信息。在SolidWorks中的零件文档有材料信息,实体(Body)有材质信息,面有材质信息。此处的材质信息包含颜色,光泽度等等。 处理这些材质信息后才能让转换出来的文档和SolidWorks中的文档看起来相似。
4.2 材质信息
• 在SolidWorks中,IPartDoc,IFace2,IBody都具有MaterialPropertyValue的属性。这个属性会返回一个数组。
[ R, G, B, Ambient, Diffuse, Specular, Shininess, Transparency, Emission ]
• 在这里我们使用一个SharpglTF中MaterialBulider对象来构造一个的材质对象
public MaterialBuilder MaterialBuilder
{
get
{
if (FaceMaterialValue != null)
{
_MaterialBuilder = new MaterialBuilder()
.WithDoubleSide(true)
.WithChannelParam(KnownChannels.Normal, new System.Numerics.Vector4(
Convert.ToSingle(FaceMaterialValue[0]),
Convert.ToSingle(FaceMaterialValue[1]),
Convert.ToSingle(FaceMaterialValue[2]),
1));
}
return _MaterialBuilder;
}
}
4.3 存储零件,实体,面的材质和几何信息
namespace DuSwToglTF.Model
{
using VERTEX = SharpGLTF.Geometry.VertexTypes.VertexPosition;
public class SWglTFModel
{
/// <summary>
/// SolidWorks材料属性
/// </summary>
public double[] PartMaterialValue;
/// <summary>
/// Solidworks变换矩阵
/// </summary>
public MathTransform SWMathTran;
/// <summary>
/// 默认材料属性
/// </summary>
private MaterialBuilder InitMaterial = new MaterialBuilder()
.WithDoubleSide(true)
.WithMetallicRoughnessShader()
.WithChannelParam(KnownChannels.MetallicRoughness, new Vector4(1, 0, 0, 1));
/// <summary>
/// 初始原点矩阵
/// </summary>
public Matrix4x4 InitPartPos = new Matrix4x4(
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0,1
);
/// <summary>
/// 零件矩阵
/// </summary>
public Matrix4x4 PartTransform
{
get {
if (SWMathTran == null)
{
return InitPartPos;
}
else
{
double[] Tran = SWMathTran.ArrayData;
return new Matrix4x4(
Convert.ToSingle(Tran[0]), Convert.ToSingle(Tran[1]), Convert.ToSingle(Tran[2]), 0,
Convert.ToSingle(Tran[3]), Convert.ToSingle(Tran[4]), Convert.ToSingle(Tran[5]), 0,
Convert.ToSingle(Tran[6]), Convert.ToSingle(Tran[7]), Convert.ToSingle(Tran[8]), 0,
Convert.ToSingle(Tran[9]), Convert.ToSingle(Tran[10]), Convert.ToSingle(Tran[11]), 0
);
}
}
}
//[ R, G, B, Ambient, Diffuse, Specular, Shininess, Transparency, Emission ]
/// <summary>
/// 材料属性
/// </summary>
public MaterialBuilder MaterialBuilder
{
get
{
if (PartMaterialValue != null)
{
//if (PartMaterialValue[0] == 1 && PartMaterialValue[1] == 1 && PartMaterialValue[2] == 1)
//{
// return InitMaterial;
//}
//else
//{
_MaterialBuilder = new MaterialBuilder()
.WithDoubleSide(true)
.WithChannelParam(KnownChannels.BaseColor, new System.Numerics.Vector4(
Convert.ToSingle(PartMaterialValue[0]),
Convert.ToSingle(PartMaterialValue[1]),
Convert.ToSingle(PartMaterialValue[2]),
1));
// }
}
if (_MaterialBuilder == null)
{
_MaterialBuilder = InitMaterial;
}
return _MaterialBuilder;
}
}
/// <summary>
/// 所有的实体
/// </summary>
public List<BodyglTFModel> BodyList = new List<BodyglTFModel>();
#region 私有字段
private Matrix4x4 _PartTransform;
private MaterialBuilder _MaterialBuilder = null;
#endregion
}
/// <summary>
/// 实体模型属性
/// </summary>
public class BodyglTFModel
{
/// <summary>
/// 初始原点矩阵
/// </summary>
public Matrix4x4 InitPartPos = new Matrix4x4(
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1
);
public MathTransform SWMathTran;
/// <summary>
/// 零件矩阵
/// </summary>
public Matrix4x4 BodyTransform
{
get
{
if (SWMathTran == null)
{
return InitPartPos;
}
else
{
double[] Tran = SWMathTran.ArrayData;
return new Matrix4x4(
Convert.ToSingle(Tran[0]), Convert.ToSingle(Tran[1]), Convert.ToSingle(Tran[2]), 0,
Convert.ToSingle(Tran[3]), Convert.ToSingle(Tran[4]), Convert.ToSingle(Tran[5]), 0,
Convert.ToSingle(Tran[6]), Convert.ToSingle(Tran[7]), Convert.ToSingle(Tran[8]), 0,
Convert.ToSingle(Tran[9]), Convert.ToSingle(Tran[10]), Convert.ToSingle(Tran[11]), 0
);
}
}
}
/// <summary>
/// 实体材料属性
/// </summary>
public double[] BodyMaterialValue;
//[ R, G, B, Ambient, Diffuse, Specular, Shininess, Transparency, Emission ]
public MaterialBuilder MaterialBuilder
{
get
{
if (BodyMaterialValue != null)
{
_MaterialBuilder = new MaterialBuilder()
.WithDoubleSide(true)
.WithChannelParam(KnownChannels.Normal, new System.Numerics.Vector4(
Convert.ToSingle(BodyMaterialValue[0]),
Convert.ToSingle(BodyMaterialValue[1]),
Convert.ToSingle(BodyMaterialValue[2]),
1));
}
return _MaterialBuilder;
}
}
/// <summary>
/// 实体中所有的面
/// </summary>
public List<FaceglTFModel> FaceList = new List<FaceglTFModel>();
private MaterialBuilder _MaterialBuilder = null;
}
public class FaceglTFModel
{
/// <summary>
/// 面材质属性
/// </summary>
public double[] FaceMaterialValue;
private MaterialBuilder _MaterialBuilder = null;
//[ R, G, B, Ambient, Diffuse, Specular, Shininess, Transparency, Emission ]
public MaterialBuilder MaterialBuilder
{
get
{
if (FaceMaterialValue != null)
{
_MaterialBuilder = new MaterialBuilder()
.WithDoubleSide(true)
.WithChannelParam(KnownChannels.Normal, new System.Numerics.Vector4(
Convert.ToSingle(FaceMaterialValue[0]),
Convert.ToSingle(FaceMaterialValue[1]),
Convert.ToSingle(FaceMaterialValue[2]),
1));
}
return _MaterialBuilder;
}
}
/// <summary>
/// 面中所有的三角面
/// </summary>
public List<FaceVertexModel> FaceTri = new List<FaceVertexModel>();
}
/// <summary>
/// 一个三角形面的三个顶点
/// </summary>
public class FaceVertexModel
{
public VERTEX a;
public VERTEX b;
public VERTEX c;
}
}
• 我们用这样一个数据结构来描述一个SolidWorks零件或者转配体。在转换中将其转换为SharpglTF的一个sence对象,在输出为gltf格式。
五.转换为glTF格式
5.1.转换为gltf
• 在完成信息提取后,我们只需要用SharpglTF库来实现对gltf格式的操作即可
public List<string> SaveAs(SWglTFModel Model, string Path, string Name)
{
var scene = new SharpGLTF.Scenes.SceneBuilder();
foreach (var Body in Model.BodyList)
{
//创建一个网格
var Mesh = new MeshBuilder<VERTEX>("mesh");
var material = (Body.MaterialBuilder == null ? Model.MaterialBuilder : Body.MaterialBuilder);
if (material == null)
{
material = new MaterialBuilder()
.WithDoubleSide(true)
.WithMetallicRoughnessShader()
.WithChannelParam("BaseColor", new Vector4(1, 0, 0, 1));
}
//确定材质属性
var prim = Mesh.UsePrimitive(material
);
foreach (var face in Body.FaceList)
{
foreach (var tri in face.FaceTri)
{
prim.AddTriangle(tri.a, tri.b, tri.c);
}
}
scene.AddMesh(Mesh, Body.BodyTransform);
}
var model = scene.ToSchema2();
model.SaveAsWavefront(Path + "" + Name + ".obj");
model.SaveGLB(Path + "" + Name + ".glb");
model.SaveGLTF(Path + "" + Name + ".gltf");
return new List<string>()
{
Path + "" + Name + ".obj",
Path + "" + Name + ".glb",
Path + "" + Name + ".gltf"
};
}
• SharpglTF帮我们完成了对gltf中JSON格式的序列化,这使我们无需过多关注gltf格式的内部信息。但我们仍然需要理解一下gltf格式的各个节点。
六.装配体和多实体格式的转换
SolidWorks作为一个优秀的参数化软件,其格式和信息是相当复杂的。我们转换中只处理了实体(Body)的网格信息和颜色信息,材质中一些光泽度,粗糙度等都没考虑,场景,运动动画等也没考虑。 多实体中实体的隐藏等也未考虑。
同样,在装配体的转换中,目前也只是将其作为多实体来考虑,其中零部件的运动仿真信息,变换信息,零部件隐藏是否隐藏,缺失等也未考虑。
6.1 文档处理
• 在转换开始前,我们要辨别一下文档类型,将不能转换的文档类型提示用户。
public SWglTFModel ConvertToglTFModel(ModelDoc2 swModel,out ErrorType errors)
{
SWglTFModel model = null;
errors = ErrorType.DoNotKnow;
ProgressStatus_event(0.01, "开始读取", ErrorType.NoErros);
swDocumentTypes_e ModelType = (swDocumentTypes_e)swModel.GetType();
switch (ModelType)
{
case swDocumentTypes_e.swDocNONE:
errors = ErrorType.NoPartOrAssemblyDocument;
return null;
case swDocumentTypes_e.swDocPART:
ProgressStatus_event(0.02, "读取零件信息", ErrorType.NoErros);
model = ConvertPartToglTF(swModel);
ProgressStatus_event(0.5, "读取零件信息完成", ErrorType.NoErros);
break;
case swDocumentTypes_e.swDocASSEMBLY:
ProgressStatus_event(0.02, "读取装配体信息", ErrorType.NoErros);
model = ConvertAssemblyToglTF(swModel);
ProgressStatus_event(0.5, "读取装配体信息完成", ErrorType.NoErros);
break;
case swDocumentTypes_e.swDocDRAWING:
errors = ErrorType.NoPartOrAssemblyDocument;
return null;
case swDocumentTypes_e.swDocSDM:
errors = ErrorType.NoPartOrAssemblyDocument;
return null;
case swDocumentTypes_e.swDocLAYOUT:
errors = ErrorType.NoPartOrAssemblyDocument;
return null;
case swDocumentTypes_e.swDocIMPORTED_PART:
break;
case swDocumentTypes_e.swDocIMPORTED_ASSEMBLY:
break;
}
if (model == null)
{
errors = ErrorType.DoNotKnow;
}
return model;
}
• 定义一个枚举类型
public enum ErrorType
{
NoErros,
NoPartOrAssemblyDocument,
DoNotKnow
}
6.2 零件转换
• 将零件默认安装多实体转换(单个实体的情况居多)
private SWglTFModel ConvertPartToglTF(ModelDoc2 swModel)
{
SWglTFModel Model = new SWglTFModel();
var MaterialValue = ((PartDoc)swModel).MaterialPropertyValues;
if (MaterialValue != null)
{
Model.PartMaterialValue = MaterialValue;
}
object[] bodys = ((PartDoc)swModel).GetBodies((int)swBodyType_e.swAllBodies);
foreach (Body2 swBody in bodys)
{
Model.BodyList.Add(GetglTFBodyModel(swBody));
}
return Model;
}
6.3 装配体转换
private SWglTFModel ConvertAssemblyToglTF(ModelDoc2 swModel)
{
SWglTFModel Model = new SWglTFModel();
AssemblyDoc swAssDoc = (AssemblyDoc)swModel;
object[] comps = swAssDoc.GetComponents(false);
foreach (Component2 item in comps)
{
double[] MaterialValue = item.MaterialPropertyValues;
if (MaterialValue == null)
{
ModelDoc2 swCompModel = item.GetModelDoc2();
if (swCompModel!= null)
{
MaterialValue = swCompModel.MaterialPropertyValues;
}
}
object[] bodys = item.GetBodies2((int)swBodyType_e.swAllBodies);
if (bodys != null)
{
foreach (Body2 swBody in bodys)
{
var bodymodel = GetglTFBodyModel(swBody);
if (bodymodel.BodyMaterialValue == null && MaterialValue !=null)
{
bodymodel.BodyMaterialValue = MaterialValue;
}
bodymodel.SWMathTran = item.Transform2;
Model.BodyList.Add(bodymodel);
}
}
}
return Model;
}
到目前为止,对装配体的转换还存在一定问题。本文只是作为一个格式转换的示例,如果要作为一个完整的转换器,还有很多工作要做。
SolidWorks文件转换为glTF格式插件mp.weixin.qq.com![v2-62bdd3bba7a5b2dc91bf4542fb509a76_180x120.jpg](http://img-03.proxy.5ce.com/view/image?&type=2&guid=831cfd4a-452f-eb11-8da9-e4434bdf6706&url=https://pic3.zhimg.com/v2-62bdd3bba7a5b2dc91bf4542fb509a76_180x120.jpg)