Files
SzmediTools/Szmedi.RvKits/Modeling/TrackCreatorViewModel.cs
2025-12-23 21:37:02 +08:00

670 lines
30 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Windows;
using System.Windows.Data;
using Autodesk.Revit.DB;
using Autodesk.Revit.UI.Selection;
using Nice3point.Revit.Toolkit.External.Handlers;
using Szmedi.RvKits.Assists;
using Szmedi.RvKits.Properties;
namespace Szmedi.RvKits.Modeling
{
public partial class TrackCreatorViewModel : ObservableValidator
{
[ObservableProperty]
private string searchProfileText;
partial void OnSearchProfileTextChanged(string value)
{
if (ProfileFamilyTypes != null)
{
ProfileFamilyTypesView.Filter = obj =>
{
if (obj is WrapperSymbol ws)
{
return ws.FullName.Contains(value);
}
return false;
};
ProfileFamilyTypesView.Refresh();
}
}
[ObservableProperty]
private string searchFamilyText;
partial void OnSearchFamilyTextChanged(string value)
{
if (FamilyTypes != null)
{
FamilyTypesView.Filter = obj =>
{
if (obj is WrapperSymbol ws)
{
return ws.FullName.Contains(value);
}
return false;
};
FamilyTypesView.Refresh();
}
}
[ObservableProperty]
private string searchMaterialText;
partial void OnSearchMaterialTextChanged(string value)
{
if (Materials != null)
{
MaterialsView.Filter = obj =>
{
if (obj is Material mat)
{
return mat.Name.Contains(value);
}
return false;
};
MaterialsView.Refresh();
}
}
[ObservableProperty]
private CenterCurveType centerCurveType;
[ObservableProperty]
public partial List<WrapperSymbol> FamilyTypes { get; set; }
private readonly ActionEventHandler handler;
[Required(ErrorMessage = "不可为空")]
[IsNumeric]
[ObservableProperty]
private double instanceOffsetX = 717.5;
[ObservableProperty]
[Required(ErrorMessage = "不可为空")]
[IsNumeric]
private double instanceOffsetY = -216;
[ObservableProperty]
private bool isModelCurve = true;
[ObservableProperty]
[NotifyCanExecuteChangedFor(nameof(CreateTrackCommand))]
private bool isRunning;
[ObservableProperty]
private bool isTwoSides = false;
[ObservableProperty]
private List<Material> materials;
[Required(ErrorMessage = "不可为空")]
[IsNumeric]
[ObservableProperty]
private double offsetX = 717.5;
[ObservableProperty]
[Required(ErrorMessage = "不可为空")]
[IsNumeric]
private double offsetY = 0;
[Required(ErrorMessage = "不可为空")]
[IsNumeric]
[Minimum(0.5)]
[ObservableProperty]
[NotifyDataErrorInfo]
private double precision = 1;
[ObservableProperty]
private System.Windows.Media.ImageSource previewImage = Resources.TrackPic.ToBitmapSource();
[ObservableProperty]
public partial List<WrapperSymbol> ProfileFamilyTypes { get; set; }
public ICollectionView ProfileFamilyTypesView { get; set; }
public ICollectionView FamilyTypesView { get; set; }
public ICollectionView MaterialsView { get; set; }
[ObservableProperty]
[NotifyCanExecuteChangedFor(nameof(CreateTrackCommand))]
public partial WrapperSymbol SelectedFamilyType { get; set; }
[ObservableProperty]
public partial Material SelectedMaterial { get; set; }
[ObservableProperty]
[NotifyCanExecuteChangedFor(nameof(CreateTrackCommand))]
public partial WrapperSymbol SelectedProfileFamilyType { get; set; }
[Required(ErrorMessage = "不可为空")]
[IsNumeric]
[Minimum(100)]
[NotifyDataErrorInfo]
[ObservableProperty]
private double spacing = 900;
public TrackCreatorViewModel(Document doc)
{
handler = new();
ProfileFamilyTypes = doc.QueryElementsByTypeAndCategory<FamilySymbol>(BuiltInCategory.OST_ProfileFamilies)
.Cast<FamilySymbol>()
.OrderBy(n => n.FamilyName)
.ToList().ConvertAll(s => new WrapperSymbol(s));
FamilyTypes = doc.QueryElementsByType<FamilySymbol>()
.OfType<FamilySymbol>()
.Where(
s =>
s.Category.Parent == null
&& s.Family.IsEditable
&& s.Family.IsUserCreated
&& s.Family.FamilyPlacementType == FamilyPlacementType.OneLevelBased
)
.OrderBy(n => n.FamilyName)
.ToList().ConvertAll(s => new WrapperSymbol(s));
Materials = doc.QueryElementsByType<Material>().OrderBy(n => n.Name).OfType<Material>().ToList();
ProfileFamilyTypesView = CollectionViewSource.GetDefaultView(ProfileFamilyTypes);
FamilyTypesView = CollectionViewSource.GetDefaultView(FamilyTypes);
MaterialsView = CollectionViewSource.GetDefaultView(Materials);
}
[RelayCommand]
private void ClearFmailyTypeSelection()
{
if (SelectedFamilyType != null)
SelectedFamilyType = null;
}
[RelayCommand]
private void ClearMaterialSelection()
{
if (SelectedMaterial != null)
SelectedMaterial = null;
}
[RelayCommand]
private void ClearProfilesSelection()
{
if (SelectedProfileFamilyType != null)
SelectedProfileFamilyType = null;
}
[RelayCommand(CanExecute = nameof(CanCreate))]
private void CreateTrack()
{
handler.Raise(uiapp =>
{
var UiDocument = uiapp.ActiveUIDocument;
var doc = UiDocument.Document;
List<Curve> curves = null;
try
{
IsRunning = true;
switch (CenterCurveType)
{
case CenterCurveType.ModelCurve:
{
var refers = UiDocument.Selection.PickObjects(
ObjectType.Element,
new GenericFilter<ModelCurve>(),
"请选择模型线并完成选择"
);
curves = refers.Select(r => doc.GetElement(r)).OfType<ModelCurve>().Select(mc => mc.GeometryCurve).ToList();
var loops = CurveUtils.GroupContinuousCurves(curves);
if (loops.Count != 1)
{
MessageBox.Show("未选择路径或所选线条不止一条路径", "错误");
IsRunning = false;
return;
}
curves = loops.FirstOrDefault();
break;
}
case CenterCurveType.MassCurve:
{
var refer = UiDocument.Selection.PickObject(
ObjectType.Element,
new FuncFilter(e => e is FamilyInstance importInstance && importInstance.Category.ToBuiltInCategory() == BuiltInCategory.OST_Mass),
"请选择仅包含一条连续曲线的体量族"
);
var instance = doc.GetElement(refer) as FamilyInstance;
var geo = instance.GetGeometryObjects().FirstOrDefault();
if (geo is GeometryInstance geometryInstance)
{
curves = geometryInstance.GetInstanceGeometry().Where(g => g is Curve).Cast<Curve>().ToList();
}
break;
}
case CenterCurveType.DWGCurve:
{
var refer = UiDocument.Selection.PickObject(
ObjectType.Element,
new FuncFilter(e => e is ImportInstance importInstance && importInstance.Category.Name.EndsWith(".dwg")),
"请选择dwg实例"
);
var importInstance = doc.GetElement(refer) as ImportInstance;
//curves = importInstance.GetCurves();
var geo = importInstance.GetGeometryObjects().FirstOrDefault();
if (geo is GeometryInstance geometryInstance)
{
var polyLine = geometryInstance.GetInstanceGeometry().FirstOrDefault() as PolyLine;
curves = [polyLine.ConverToHermiteSpline()];
}
break;
}
default:
break;
}
//if (IsModelCurve)
//{
// IList<Reference> refers = UiDocument.Selection.PickObjects(
// ObjectType.Element,
// new GenericFilter<ModelCurve>(),
// "请选择模型线并完成选择"
// );
// curves = refers.Select(r => doc.GetElement(r)).OfType<ModelCurve>().Select(mc => mc.GeometryCurve).ToList();
// List<List<Curve>> loops = new CurveUtils().GetCurvesOfLoops(curves);
// if (loops.Count != 1)
// {
// MessageBox.Show("未选择路径或所选线条不止一条路径", "错误");
// IsRunning = false;
// return;
// }
// curves = loops.FirstOrDefault();
//}
//else
//{
// Reference refer = UiDocument.Selection.PickObject(
// ObjectType.Element,
// new FuncFilter(e => e is ImportInstance importInstance && importInstance.Category.Name.EndsWith(".dwg")),
// "请选择dwg实例"
// );
// ImportInstance importInstance = doc.GetElement(refer) as ImportInstance;
// curves = importInstance.GetCurves();
//}
}
catch (Autodesk.Revit.Exceptions.OperationCanceledException)
{
IsRunning = false;
return;
}
try
{
if ((curves != null) && (curves.Count > 1))
{
CurveUtils.SortCurvesContiguous(curves);
var loop = CurveLoop.Create(curves);
if (!loop.IsOpen())
{
MessageBox.Show("选择的模型线是闭合的,无法使用", "提示");
IsRunning = false;
return;
}
var cs = loop.GetEnumerator();
curves = [];
while (cs.MoveNext())
{
curves.Add(cs.Current);
}
}
}
catch (Autodesk.Revit.Exceptions.ArgumentException)
{
MessageBox.Show("所选模型线不连续或包含螺旋线", "错误");
IsRunning = false;
return;
}
List<XYZ> points = [];
var imperial = Precision * 1000 / 304.8;
for (var i = 0; i < curves.Count; i++)
{
var curve = curves[i];
var divide = (int)Math.Floor(curve.Length / imperial);
//长度小于最小细分间距
//始终都会添加起始点
if (divide == 0)
{
points.Add(curve.Evaluate(0, true));
}
else
{
for (double j = 0; j < divide; j++)
{
var x = j / divide;
points.Add(curve.Evaluate(x, true));
}
}
//var li = curves[i].Tessellate();
//li.Remove(li.Last());
//points.AddRange(li);
}
points.Add(curves[curves.Count - 1].GetEndPoint(1));
var planePoints = points.Select(p => new XYZ(p.X, p.Y, 0)).ToList();
var spline3D = HermiteSpline.Create(points, false);
doc.InvokeGroup(
_ =>
{
//SketchPlane sketch = SketchPlane.Create(doc, Plane.CreateByNormalAndOrigin(XYZ.BasisZ, spline3D.GetEndPoint(0)));
//var mc = doc.Create.NewModelCurve(spline3D, sketch);
try
{
//实例布置
if (SelectedFamilyType != null)
{
doc.Invoke(
_ =>
{
var horizonSpline = HermiteSpline.Create(planePoints, false);
if (!SelectedFamilyType.FamilySymbol.IsActive)
{
SelectedFamilyType.FamilySymbol.Activate();
}
//找到零标高
var level = new FilteredElementCollector(doc)
.OfClass(typeof(Level))
.Cast<Level>()
.FirstOrDefault(l => Math.Abs(l.Elevation) < 0.00001);
List<Autodesk.Revit.Creation.FamilyInstanceCreationData> dataList = [];
var spacing = Spacing / 304.8;
var x = InstanceOffsetX / 304.8;
var y = InstanceOffsetY / 304.8;
var n = Math.Floor(horizonSpline.Length / spacing);
for (var i = 0; i < n; i++)
{
//XYZ pointSplie = spline3D.Evaluate(spacing * i, false);
var pointxoy = horizonSpline.Evaluate(spacing * i, false);
var unboundLine = Line.CreateUnbound(pointxoy, XYZ.BasisZ);
var point = unboundLine.IntersectionPoint(spline3D);
var transform = spline3D.ComputeDerivatives(spacing * i, false);
//族的参考方向
var referVector = transform.BasisX.CrossProduct(XYZ.BasisZ);
if (IsTwoSides)
{
var locLeft = point.Add(-referVector.Normalize() * x) + y * XYZ.BasisZ;
Autodesk.Revit.Creation.FamilyInstanceCreationData data1 =
new(
locLeft,
SelectedFamilyType.FamilySymbol,
referVector,
level,
Autodesk.Revit.DB.Structure.StructuralType.Footing
);
dataList.Add(data1);
}
var loc = point.Add(referVector.Normalize() * x) + y * XYZ.BasisZ;
Autodesk.Revit.Creation.FamilyInstanceCreationData data =
new(
loc,
SelectedFamilyType.FamilySymbol,
referVector,
level,
Autodesk.Revit.DB.Structure.StructuralType.Footing
);
dataList.Add(data);
}
doc.Create.NewFamilyInstances2(dataList);
IsRunning = false;
},
"创建轨枕、扣件"
);
}
}
catch (Exception ex)
{
LogAssists.WriteLog(ex.StackTrace);
MessageBox.Show(ex.Message, "实例布置失败", MessageBoxButton.OK, MessageBoxImage.Error);
IsRunning = false;
}
Document familyDocument = null;
try
{
//轮廓放样
if (SelectedProfileFamilyType == null)
{
IsRunning = false;
return;
}
var point = spline3D.GetEndPoint(0);
//非刚性变换,会改变原几何,此变换在曲线计算导数时为非刚性变换各个basis的模不等于1
var transform = spline3D.ComputeDerivatives(0, false);
familyDocument = doc.EditFamily(SelectedProfileFamilyType.FamilySymbol.Family);
XYZ tangent;
XYZ rightBasis;
XYZ topBasis;
tangent = transform.BasisX.Normalize();
rightBasis = tangent.CrossProduct(XYZ.BasisZ).Normalize();
topBasis = rightBasis.CrossProduct(tangent).Normalize();
if (!XYZ.IsWithinLengthLimits(point))
{
MessageBox.Show("点不在Revit模型空间内,内部原点坐标与模型线相距太远", "错误");
return;
}
//Plane plane = Plane.CreateByOriginAndBasis(point, rightBasis, topBasis);
//var d0 = transform.BasisX.CrossProduct(XYZ.BasisZ).DotProduct(transform.BasisX);
//var translate = Transform.CreateTranslation(transform.Origin);
//var rotate = Transform.CreateRotationAtPoint(
// -rightBasis,
// XYZ.BasisZ.AngleTo(transform.BasisX),
// XYZ.Zero);
//double det = transform.Determinant;
//Transform profileTransform = rotate.Multiply(translate);
//构造新的变换
var profileTransform = Transform.Identity;
profileTransform.Origin = point;
profileTransform.BasisX = rightBasis;
profileTransform.BasisY = topBasis;
profileTransform.BasisZ = tangent;
//var d = rightBasis.DotProduct(transform.BasisX);
//if (!profileTransform.IsConformal)
//{
// MessageBox.Show($"起始段必须是直线", "创建失败", MessageBoxButton.OK, MessageBoxImage.Error);
// IsRunning = false;
// return;
//}
//偏移变换
var offsetTransform = Transform.CreateTranslation(new XYZ(OffsetX, OffsetY, 0) / 304.8);
//修改类型以适应选择的族类型,保证几何与族类型一致
familyDocument.Invoke(_ =>
{
var fm = familyDocument.FamilyManager;
foreach (FamilyType type in fm.Types)
{
if (type.Name == SelectedProfileFamilyType.FamilySymbol.Name)
{
fm.CurrentType = type;
break;
}
}
});
//获取截面
var profiles = familyDocument
.QueryElementsByType<CurveElement>()
.OfType<CurveElement>()
.Select(c => c.GeometryCurve)
.ToList();
#if DEBUG
//doc.Invoke(_ =>
//{
// SketchPlane sketch = SketchPlane.Create(
// doc,
// Plane.CreateByNormalAndOrigin(XYZ.BasisZ, spline3D.GetEndPoint(0))
// );
// var mc = doc.Create.NewModelCurve(spline3D, sketch);
//});
#endif
//doc.Invoke(
// _ =>
// {
// SketchPlane sketch = SketchPlane.Create(doc, Plane.CreateByNormalAndOrigin(XYZ.BasisZ, XYZ.Zero));
// foreach (var item in profiles)
// {
// var mc = doc.Create.NewModelCurve(item, sketch);
// }
// });
doc.Invoke(
_ =>
{
//if (!SelectedProfileFamilyType.IsActiveZoom)
//{
// SelectedProfileFamilyType.Activate();
//}
//if (loop.IsOpen())
//{
// return;
//}
//familyDocument.FamilyCreate.NewSweep
//材质处理
Solid solidMirror = null;
SolidOptions options = new(
SelectedMaterial == null ? ElementId.InvalidElementId : SelectedMaterial.Id,
ElementId.InvalidElementId
);
//获取截面的曲线集合
var list = CurveUtils.GroupContinuousCurves(profiles);
//生成线串
var loops = list.Select(cs => CurveLoop.Create(cs)).ToList();
if (IsTwoSides)
{
List<CurveLoop> loopsAnother = [];
var offsetTransformAnother = Transform.CreateTranslation(new XYZ(-OffsetX, OffsetY, 0) / 304.8);
foreach (var item in loops)
{
loopsAnother.Add(CurveLoop.CreateViaCopy(item));
}
foreach (var item in loopsAnother)
{
item.Transform(offsetTransformAnother);
item.Transform(profileTransform);
}
solidMirror = GeometryCreationUtilities.CreateFixedReferenceSweptGeometry(
CurveLoop.Create([spline3D]),
0,
0,
loopsAnother,
XYZ.BasisZ,
options
);
}
foreach (var item in loops)
{
item.Transform(offsetTransform);
item.Transform(profileTransform);
}
var solid = GeometryCreationUtilities.CreateFixedReferenceSweptGeometry(
CurveLoop.Create([spline3D]),
0,
0,
loops,
XYZ.BasisZ,
options
);
List<GeometryObject> geos = [solid];
if (IsTwoSides && solidMirror != null)
{
geos.Add(solidMirror);
}
doc.CreateDirectShapeInstance(
"轨道",
BuiltInCategory.OST_GenericModel,
geos);
//var shape = DirectShape.CreateElement(doc, new ElementId(BuiltInCategory.OST_GenericModel));
//shape.SetShape(geos);
},
"创建钢轨"
);
IsRunning = false;
familyDocument.Close(false);
//spline3D.CreateOffset();
//SketchPlane sketch = SketchPlane.Create(doc, Plane.CreateByNormalAndOrigin(p4, p1));
//var mc = doc.Create.NewModelCurve(spline3D, sketch);
}
catch (Exception ex)
{
familyDocument?.Close(false);
LogAssists.WriteLog(ex.StackTrace);
MessageBox.Show(ex.Message, "轮廓放样失败", MessageBoxButton.OK, MessageBoxImage.Error);
IsRunning = false;
}
},
"创建轨道"
);
});
}
partial void OnCenterCurveTypeChanged(CenterCurveType value)
{
switch (value)
{
case CenterCurveType.ModelCurve:
Precision = 1;
break;
case CenterCurveType.MassCurve:
Precision = 1;
break;
case CenterCurveType.DWGCurve:
Precision = 2;
break;
default:
break;
}
}
private bool CanCreate => !IsRunning && !HasErrors && (SelectedProfileFamilyType != null || SelectedFamilyType != null);
//public double Precision
//{
// get { return precision; }
// set { SetProperty(ref precision, value, true); }
//}
//public double Spacing
//{
// get { return spacing; }
// set { SetProperty(ref spacing, value, true); }
//}
}
public class WrapperSymbol
{
public FamilySymbol FamilySymbol { get; }
public WrapperSymbol(FamilySymbol familySymbol)
{
FamilySymbol = familySymbol;
}
public string FullName => $"{FamilySymbol.FamilyName} : {FamilySymbol.Name}";
public override string ToString()
{
return $"{FamilySymbol.FamilyName} : {FamilySymbol.Name}";
}
}
public enum CenterCurveType
{
ModelCurve,
MassCurve,
DWGCurve,
}
}