solidworks自动标注折弯3 OneBend Select4 GetVisibleComponents

【solidworks自动标注折弯3】 https://www.bilibili.com/video/BV1azdLBTE5E/?share_source=copy_web\&vd_source=6b8044752c092314d4b426afb8ebd370

这段代码是一个基于 SolidWorks API (C#) 开发的插件工具,主要用于在工程图中自动识别折弯(Bend)特征,并为其添加尺寸标注。

从整体来看,这是一段逻辑严谨、工程化程度较高 的代码,它解决了参数化建模中常见的"自动标注"痛点。代码不仅实现了基础的几何识别,还通过 HashSet 去重机制处理了复杂的拓扑关系,避免了重复标注。

以下是对该代码的详细评价:

1. 核心功能与逻辑架构

代码的核心功能是 ProcessBend 方法,其逻辑流程非常清晰,遵循了典型的几何处理步骤:

  1. 特征识别 :遍历模型中的 OneBend 特征,锁定需要处理的对象。
  2. 几何提取 :在折弯特征中寻找面积最大的圆柱面(CylFace),以此作为折弯轴线的基准。
  3. 拓扑遍历
    • 找到与轴线平行的直边(ParallelEdges)。
    • 通过直边找到相邻的"一级面"(通常是折弯的两个延伸面)。
    • 在一级面中寻找最远的平行边,进而找到"二级面"。
  4. 智能判断与递归:代码中有一个亮点是处理了二级面如果是圆柱面的情况,会继续寻找"三级面"。这说明开发者考虑到了复杂的多级折弯或圆角过渡的情况。
  5. 标注生成 :寻找平行面,计算视图位置,并调用 AddDimension2 进行标注。

2. 代码质量与亮点 ✨

  • 防重复机制(去重逻辑)
    代码使用了 HashSet<string> dimensionedPairs 来存储已标注的面组合键(GetFacePairKey)。这是非常必要的,因为在遍历多个折弯时,共享的平面很容易导致重复标注。通过 GetHashCode 排序生成键值,确保了 (A,B)(B,A) 被视为同一组,逻辑非常严密。
  • 视图与模型的坐标映射
    代码处理了 SolidWorks 中常见的难题------3D模型坐标与2D视图坐标的转换 。在 FindVisibleEdge 方法中,它没有简单地依赖 COM 对象的引用(因为视图中的边是模型边的投影,引用通常不同),而是通过端点坐标的数值比对IsPointClose)来匹配可见边。这是一种非常稳健的处理方式,避免了因 COM 封送(Marshaling)导致的对象引用失效问题。
  • 容错与日志
    大量的 Console.WriteLine 输出和 try-catch 块提供了良好的调试信息,方便开发者在运行时了解程序卡在哪一步(例如"找到 X 条边"、"跳过不可见面")。
  • 数学计算准确
    IsParallelPointToAxisDistance 方法展示了扎实的几何数学基础,使用了向量点积和投影来判断平行度和距离,而非简单的坐标比较,保证了算法在不同角度下的鲁棒性。

3. 潜在改进点与优化建议 ⚙️

尽管代码质量很高,但在实际工程应用中,仍有以下优化空间:

维度 现状分析 改进建议
用户交互 目前依赖 Console.WriteLine 输出信息,且强制 CommandInProgress = true 建议改为模态窗体任务窗格,提供更友好的进度条和错误提示,或者将日志写入文件而非控制台。
性能优化 每次标注都调用 AddDimension2,且涉及多次遍历。 对于大型装配体,建议增加批量处理模式,或者在标注前进行更严格的几何筛选,减少不必要的遍历。
配置灵活性 偏移量 offset 和容差值(如 0.001)写死在代码中。 建议提取为配置文件用户参数,允许用户自定义标注样式、字体大小和搜索容差。
异常处理 虽有 try-catch,但部分逻辑(如 GetBodies2 返回 null)处理较为简单。 可以增加更细粒度的异常分类,区分"无文档"、"非工程图"、"非零件"等不同错误类型,并给出针对性的用户提示。
视图定位 标注位置的计算逻辑(bounds[0] + offset)较为简单。 增加智能避让算法,检测视图中已有的尺寸,自动寻找空白区域放置新尺寸,避免尺寸线重叠。

4. 总结

这段代码是一个高质量的 SolidWorks 二次开发范例

它不仅仅是一个简单的脚本,而是一个具备了几何识别、拓扑遍历、坐标变换、去重逻辑的完整工具。开发者对 SolidWorks API 的底层机制(如 COM 对象的特性、视图与模型的关系)有深刻的理解。

适用场景: 适用于钣金件设计流程自动化,特别是针对包含大量折弯特征的零件,能显著减少工程师手动标注尺寸的时间。

评分: ⭐⭐⭐⭐ (4/5) ------ 扣分点主要在于缺乏用户配置界面和硬编码的参数,但在核心算法和逻辑实现上表现优异。

csharp 复制代码
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using SolidWorks.Interop.sldworks;
using SolidWorks.Interop.swconst;
using View = SolidWorks.Interop.sldworks.View;

namespace tools
{
    public class benddim
    {
        public static void AddBendDimensions(ISldWorks swApp)
        {
            try
            {
                var swModel = (ModelDoc2)swApp.ActiveDoc;
                swApp.CommandInProgress = true;
                if (swModel == null) { Console.WriteLine("没有活动文档"); return; }

                var swSelMgr = (SelectionMgr)swModel.SelectionManager;
                if (swSelMgr.GetSelectedObjectType3(1, -1) != (int)swSelectType_e.swSelDRAWINGVIEWS)
                { Console.WriteLine("请先选择一个视图"); return; }

                var view = (View)swSelMgr.GetSelectedObject(1);
                var partDoc = (PartDoc)view.ReferencedDocument;
                if (partDoc == null) { Console.WriteLine("无法获取零件文档"); return; }

                var xform = (MathTransform)view.ModelToViewTransform;
                var xformData = (double[])xform.ArrayData;
                var bounds = (double[])view.GetOutline();
                var mathUtils = swApp.IGetMathUtility();

                int count = 0;
                double offset = 0.05;
                
                // 全局已标注面组合集合,用于跨折弯去重
                var dimensionedPairs = new HashSet<string>();

                // 遍历 Body 的特征找折弯
                foreach (Body2 body in (object[])partDoc.GetBodies2((int)swBodyType_e.swSolidBody, false))
                {
                    foreach (Feature feat in (object[])body.GetFeatures())
                    {
                        var subFeat = (Feature)feat.GetFirstSubFeature();
                        while (subFeat != null)
                        {
                            if (subFeat.GetTypeName() == "OneBend" )
                            {
                                Console.WriteLine("找到折弯特征" + subFeat.Name);
                                if (ProcessBend(swModel, view, mathUtils, xform, xformData, bounds, subFeat, ref offset, dimensionedPairs))
                                    count++;
                            }

                            subFeat = (Feature)subFeat.GetNextSubFeature();
                        }
                    }
                }

                Console.WriteLine($"标注完成,共 {count} 个折弯");
            }
            catch (Exception ex)
            {
                Console.WriteLine($"错误: {ex.Message}");
            }
        }

        static bool ProcessBend(ModelDoc2 swModel, View view, MathUtility mathUtils, MathTransform xform,
            double[] xformData, double[] bounds, Feature bendFeat, ref double offset, HashSet<string> dimensionedPairs)
        {
            // 1. 找最大圆柱面
            Face cylFace = null;
            double[] axis = null;
            double[] center = null;
            double maxArea = 0;

            foreach (Face f in (object[])bendFeat.GetFaces())
            {
                var s = (Surface)f.GetSurface();
                if (s.IsCylinder())
                {
                    double area = f.GetArea();
                    if (area > maxArea)
                    {
                        maxArea = area;
                        var p = (double[])s.CylinderParams;
                        axis = new[] { p[3], p[4], p[5] };
                        center = new[] { p[0], p[1], p[2] };
                        cylFace = f;
                    }
                }
            }
            if (cylFace == null) return false;
            Console.WriteLine("最大圆柱面面积: " + maxArea * 1000000 + " mm^2");

            // 2. 找圆柱面的edge里和圆柱轴线平行的edge(直边)
            var parallelEdges = new List<Edge>();
            foreach (Edge e in (object[])cylFace.GetEdges())
            {
                var c = (Curve)e.GetCurve();
                if (c.IsLine())
                {
                    var lineParams = (double[])c.LineParams;
                    var edgeDir = new[] { lineParams[3], lineParams[4], lineParams[5] };
                    if (IsParallel(edgeDir, axis))
                    {
                        parallelEdges.Add(e);
                    }
                }
            }
            if (parallelEdges.Count == 0) return false;
            Console.WriteLine("找到 " + parallelEdges.Count + " 条与轴线平行的边");

            // 3. 用edge的相交面找下一级面
            var firstLevelFaces = new List<Face>();
            foreach (var edge in parallelEdges)
            {
                foreach (Face f in (object[])edge.GetTwoAdjacentFaces())
                {
                    if (f != cylFace && !firstLevelFaces.Contains(f))
                    {
                        firstLevelFaces.Add(f);
                    }
                }
            }
            if (firstLevelFaces.Count == 0) return false;
            Console.WriteLine("找到 " + firstLevelFaces.Count + " 个下一级面");

            // 4. 在下一级面里找与圆柱轴平行的最远的线,然后取相交面
            var secondLevelFaces = new List<Face>();
            foreach (var face in firstLevelFaces)
            {
                Edge farthestEdge = null;
                double maxDist = -1;

                foreach (Edge e in (object[])face.GetEdges())
                {
                    var c = (Curve)e.GetCurve();
                    if (c.IsLine())
                    {
                        var lineParams = (double[])c.LineParams;
                        var edgeDir = new[] { lineParams[3], lineParams[4], lineParams[5] };
                        if (IsParallel(edgeDir, axis))
                        {
                            // 计算边到圆柱中心的距离
                            var pt = new[] { lineParams[0], lineParams[1], lineParams[2] };
                            double dist = PointToAxisDistance(pt, center, axis);
                            if (dist > maxDist)
                            {
                                maxDist = dist;
                                farthestEdge = e;
                            }
                        }
                    }
                }

                if (farthestEdge != null)
                {
                    foreach (Face f in (object[])farthestEdge.GetTwoAdjacentFaces())
                    {
                        if (f != face && !secondLevelFaces.Contains(f))
                        {
                            secondLevelFaces.Add(f);
                        }
                    }
                }
            }
            if (secondLevelFaces.Count < 2) return false;
            Console.WriteLine("找到 " + secondLevelFaces.Count + " 个二级面");

            // 5. 检查二级面是否为圆柱面,如果是则获取第三级面
            var secondaryFaces = new List<(Face face, string level)>(); // 面及其级别
            foreach (var face in secondLevelFaces)
            {
                var s = (Surface)face.GetSurface();
                if (s.IsCylinder())
                {
                    Console.WriteLine("二级面为圆柱面,继续获取第三级面");
                    // 二级面是圆柱面,获取与轴线平行的边的相邻面作为三级面
                    int thirdLevelCount = 0;
                    foreach (Edge e in (object[])face.GetEdges())
                    {
                        var c = (Curve)e.GetCurve();
                        if (c.IsLine())
                        {
                            var lineParams = (double[])c.LineParams;
                            var edgeDir = new[] { lineParams[3], lineParams[4], lineParams[5] };
                            if (IsParallel(edgeDir, axis))
                            {
                                foreach (Face adjFace in (object[])e.GetTwoAdjacentFaces())
                                {
                                    // 排除:自身、一级面、折弯圆柱面
                                    if (adjFace == face || firstLevelFaces.Contains(adjFace) || adjFace == cylFace) continue;
                                    if (!secondaryFaces.Any(x => x.face == adjFace))
                                    {
                                        secondaryFaces.Add((adjFace, "三级面"));
                                        thirdLevelCount++;
                                    }
                                }
                            }
                        }
                    }
                    Console.WriteLine($"  找到 {thirdLevelCount} 个三级面");
                }
                else
                {
                    // 不是圆柱面,直接使用
                    secondaryFaces.Add((face, "二级面"));
                }
            }

            // 6. 为每个一级面找到对应的平行二级/三级面并标注
            // 使用全局已标注面集合防止重复标注(跨折弯共享)
            int dimensionCount = 0;
            
            foreach (var firstFace in firstLevelFaces)
            {
                var s1 = (Surface)firstFace.GetSurface();
                if (!s1.IsPlane()) continue;
                var p1 = (double[])s1.PlaneParams;
                var n1 = new[] { p1[0], p1[1], p1[2] };

                // 为当前一级面找平行的二级/三级面
                Face matchedSecondFace = null;
                string matchedLevel = "";
                foreach (var item in secondaryFaces)
                {
                    var secFace = item.face;
                    var s2 = (Surface)secFace.GetSurface();
                    if (!s2.IsPlane()) continue;
                    var p2 = (double[])s2.PlaneParams;
                    var n2 = new[] { p2[0], p2[1], p2[2] };

                    // 检查两面是否平行(法向量平行)且不是同一个面
                    if (IsParallel(n1, n2) && secFace != firstFace)
                    {
                        // 生成唯一键:两面法向量、面积和配对级别的组合
                        var pairKey = GetFacePairKey(firstFace, secFace, item.level);
                        if (dimensionedPairs.Contains(pairKey))
                        {
                            Console.WriteLine($"跳过已标注的平行面组合:一级面面积 = {firstFace.GetArea() * 1000000:F2} mm^2, {item.level}面积 = {secFace.GetArea() * 1000000:F2} mm^2");
                            continue;
                        }
                        
                        matchedSecondFace = secFace;
                        matchedLevel = item.level;
                        dimensionedPairs.Add(pairKey);
                        Console.WriteLine($"找到一对平行面:一级面面积 = {firstFace.GetArea() * 1000000:F2} mm^2, {matchedLevel}面积 = {secFace.GetArea() * 1000000:F2} mm^2");
                        break;
                    }
                }

                if (matchedSecondFace == null) continue;

                // 确定放置方式
                string placement = GetPlacement(firstFace, xformData);
                if (placement == "none") continue;

                // 标注 - 获取3D边并在视图中查找对应可见边
                // 获取所有候选边(按长度降序),逐个尝试找到有可见边的
                var edgeCandidates1 = GetEdgesForDimension(firstFace, axis, "一级面");
                var edgeCandidates2 = GetEdgesForDimension(matchedSecondFace, axis, matchedLevel);
                
                Edge visEdge1 = null, visEdge2 = null;
                
                foreach (var e1 in edgeCandidates1)
                {
                    visEdge1 = FindVisibleEdge(view, e1);
                    if (visEdge1 != null) break;
                }
                
                if (visEdge1 == null)
                {
                    Console.WriteLine("一级面的标注边在视图中均不可见,跳过此对");
                    continue;
                }
                
                foreach (var e2 in edgeCandidates2)
                {
                    visEdge2 = FindVisibleEdge(view, e2);
                    if (visEdge2 != null) break;
                }
                
                if (visEdge2 == null)
                {
                    Console.WriteLine("二级面的标注边在视图中均不可见,跳过此对");
                    continue;
                }

                // 创建 SelectData 并设置视图上下文
                var selMgr = (SelectionMgr)swModel.SelectionManager;
                var selData = selMgr.CreateSelectData();
                selData.View = view;

                // 将可见边转换为 Entity 并选择
                ((Entity)visEdge1).Select4(true, selData);
                ((Entity)visEdge2).Select4(true, selData);

                double x, y;
                if (placement == "h")
                {
                    x = (bounds[0] + bounds[2]) / 2;
                    y = bounds[3] - offset;
                }
                else
                {
                    y = (bounds[1] + bounds[3]) / 2;
                    x = bounds[0] + offset;
                }
                swModel.AddDimension2(x, y, 0);
                offset += 0.005;
                swModel.ClearSelection2(true);
                dimensionCount++;
            }

            return dimensionCount > 0;
        }

        static bool IsParallel(double[] a, double[] b)
        {
            double dot = Math.Abs(a[0] * b[0] + a[1] * b[1] + a[2] * b[2]);
            double ma = Math.Sqrt(a[0] * a[0] + a[1] * a[1] + a[2] * a[2]);
            double mb = Math.Sqrt(b[0] * b[0] + b[1] * b[1] + b[2] * b[2]);
            return Math.Abs(dot - ma * mb) < 0.001;
        }

        static double PointToAxisDistance(double[] point, double[] axisCenter, double[] axisDir)
        {
            // 计算点到轴线的距离
            // 向量从轴线中心指向点
            double[] v = { point[0] - axisCenter[0], point[1] - axisCenter[1], point[2] - axisCenter[2] };
            
            // 投影到轴线方向
            double dot = v[0] * axisDir[0] + v[1] * axisDir[1] + v[2] * axisDir[2];
            double axisLen = Math.Sqrt(axisDir[0] * axisDir[0] + axisDir[1] * axisDir[1] + axisDir[2] * axisDir[2]);
            double proj = dot / axisLen;
            
            // 投影点
            double[] projPoint = {
                axisCenter[0] + proj * axisDir[0] / axisLen,
                axisCenter[1] + proj * axisDir[1] / axisLen,
                axisCenter[2] + proj * axisDir[2] / axisLen
            };
            
            // 距离
            double dx = point[0] - projPoint[0];
            double dy = point[1] - projPoint[1];
            double dz = point[2] - projPoint[2];
            return Math.Sqrt(dx * dx + dy * dy + dz * dz);
        }

        static string GetPlacement(Face face, double[] xf)
        {
            var s = (Surface)face.GetSurface();
            if (!s.IsPlane()) return "none";
            var p = (double[])s.PlaneParams;
            double nx = Math.Abs(p[0]), ny = Math.Abs(p[1]), nz = Math.Abs(p[2]);

            if (nx > ny && nx > nz)
                return Math.Round(xf[0]) != 0 ? "h" : (Math.Round(xf[1]) != 0 ? "v" : "none");
            if (ny > nx && ny > nz)
                return Math.Round(xf[3]) != 0 ? "h" : (Math.Round(xf[4]) != 0 ? "v" : "none");
            if (nz > nx && nz > ny)
                return Math.Round(xf[6]) != 0 ? "h" : (Math.Round(xf[7]) != 0 ? "v" : "none");
            return "none";
        }

        /// <summary>
        /// 获取面上所有不与轴线平行的直边,按长度降序排列(用于尺寸标注)
        /// 折弯标注需要选择不平行于折弯轴线的边,返回多条候选边供视图中逐个匹配
        /// </summary>
        static List<Edge> GetEdgesForDimension(Face face, double[] axis, string faceLevel = "")
        {
            var candidates = new List<(Edge edge, double len)>();

            foreach (Edge e in (object[])face.GetEdges())
            {
                var c = (Curve)e.GetCurve();
                if (c.IsLine())
                {
                    var lineParams = (double[])c.LineParams;
                    var edgeDir = new[] { lineParams[3], lineParams[4], lineParams[5] };
                    
                    // 跳过与轴线平行的边,折弯标注需要不平行的边
                    if (IsParallel(edgeDir, axis))
                        continue;

                    double startParam = 0, endParam = 0;
                    bool isClosed = false, isPeriodic = false;
                    c.GetEndParams(out startParam, out endParam, out isClosed, out isPeriodic);
                    double len = Math.Abs(endParam - startParam);
                    
                    candidates.Add((e, len));
                }
            }

            // 按长度降序排列,优先使用长边
            candidates.Sort((a, b) => b.len.CompareTo(a.len));
            
            var label = string.IsNullOrEmpty(faceLevel) ? "" : $"[{faceLevel}] ";
            foreach (var item in candidates)
                Console.WriteLine($"  {label}候选标注边,长度: {item.len * 1000:F2} mm");
            
            if (candidates.Count == 0)
                Console.WriteLine($"  {label}未找到不平行于轴线的直边");

            return candidates.Select(x => x.edge).ToList();
        }

        /// <summary>
        /// 在视图中查找3D边对应的可见边
        /// 按组件获取可见边(返回可转换为Edge的对象),通过3D端点坐标近似匹配解决COM封送问题
        /// 注意:IDrawingEdge接口在SolidWorks C#互操作中不存在,不能类型转换;
        ///       GetVisibleEntities(null,...) 返回的对象也是__ComObject无法转换;
        ///       正确做法是用 GetVisibleEntities(comp,...) 按组件获取,返回的对象可转换为Edge
        /// </summary>
        static Edge FindVisibleEdge(View view, Edge modelEdge)
        {
            // 获取目标3D边的端点坐标
            var startVert = (Vertex)modelEdge.GetStartVertex();
            var endVert = (Vertex)modelEdge.GetEndVertex();
            if (startVert == null || endVert == null) return null;

            var edgeStart = (double[])startVert.GetPoint();
            var edgeEnd = (double[])endVert.GetPoint();
            if (edgeStart == null || edgeEnd == null) return null;

            // 获取视图中的可见组件
            var visibleComps = (object[])view.GetVisibleComponents();
            if (visibleComps == null) return null;

            foreach (Component2 comp in visibleComps)

            {
                if (comp == null) continue;

                // 按组件获取可见边(与 GetVisibleEntities(null,...) 不同,按组件获取返回可转换为Edge的对象)
                var visibleEdges = (object[])view.GetVisibleEntities(
                    comp, (int)swViewEntityType_e.swViewEntityType_Edge);
                if (visibleEdges == null) continue;

                foreach (object obj in visibleEdges)
                {
                    if (obj == null) continue;

                    // GetVisibleEntities(comp,...) 返回的对象可转换为 Edge
                    if (!(obj is Edge visEdge)) continue;

                    try
                    {
                        // 先用引用判断(速度快)
                        if (Object.ReferenceEquals(visEdge, modelEdge)) return visEdge;

                        // 再用3D端点坐标近似判断(解决COM封送导致的引用不一致问题)
                        var meStartVert = (Vertex)visEdge.GetStartVertex();
                        var meEndVert = (Vertex)visEdge.GetEndVertex();
                        if (meStartVert == null || meEndVert == null) continue;

                        var meStart = (double[])meStartVert.GetPoint();
                        var meEnd = (double[])meEndVert.GetPoint();
                        if (meStart == null || meEnd == null) continue;

                        bool coordsMatch =
                            (IsPointClose(edgeStart, meStart) && IsPointClose(edgeEnd, meEnd))
                            ||
                            (IsPointClose(edgeStart, meEnd) && IsPointClose(edgeEnd, meStart));

                        if (coordsMatch) return visEdge;
                    }
                    catch
                    {
                        continue;
                    }
                }
            }

            return null;
        }

        /// <summary>
        /// 判断两个3D点是否在容差范围内近似相等
        /// </summary>
        static bool IsPointClose(double[] p1, double[] p2, double tol = 0.001)
        {
            if (p1 == null || p2 == null || p1.Length < 3 || p2.Length < 3) return false;
            var dx = Math.Abs(p1[0] - p2[0]);
            var dy = Math.Abs(p1[1] - p2[1]);
            var dz = Math.Abs(p1[2] - p2[2]);
            var dist = Math.Sqrt(dx * dx + dy * dy + dz * dz);
            var close = dx < tol && dy < tol && dz < tol;
           // Console.WriteLine($"[IsPointClose] p1=({p1[0]:F6},{p1[1]:F6},{p1[2]:F6}) p2=({p2[0]:F6},{p2[1]:F6},{p2[2]:F6}) dx={dx:F6} dy={dy:F6} dz={dz:F6} dist={dist:F6} tol={tol} => {close}");
            return close;
        }

        static double[] TransformPoint(MathUtility math, MathTransform xf, double[] pt)
        {
            var mp = (MathPoint)math.CreatePoint(pt);
            mp = (MathPoint)mp.MultiplyTransform(xf);
            return (double[])mp.ArrayData;
        }

        /// <summary>
        /// 生成一对面的唯一标识键,用于防止重复标注
        /// 基于Face对象的引用生成唯一键,确保只有完全相同的物理面才会被去重
        /// </summary>
        static string GetFacePairKey(Face face1, Face face2, string level)
        {
            // 使用Face对象的HashCode生成唯一标识
            // 按HashCode排序生成键,确保 (A,B) 和 (B,A) 生成相同的键
            int hash1 = face1.GetHashCode();
            int hash2 = face2.GetHashCode();
            
            var hashes = new[] { hash1, hash2 };
            Array.Sort(hashes);
            
            // 加入配对级别,确保一级对二级 与 一级对三级 不会互相去重
            return $"{hashes[0]}|{hashes[1]}|{level}";
        }
    }
}
相关推荐
njsgcs19 小时前
solidworks自动标注折弯2 找到折弯面了,差如何优雅的用面标注
solidworks
njsgcs4 天前
获得solidworks 3d零件的包围框 长宽高 boundingbox c#
开发语言·c#·solidworks
rqtz12 天前
【机器人】ROS2配置solidworks模型转换的URDF文件
ros2·urdf·solidworks
ddsoft12320 天前
制造企业PLM应用新趋势:按需部署
软件·solidworks·产品生命周期数据管理
ddsoft12322 天前
仿真应用的六大误区
仿真·solidworks
njsgcs1 个月前
solidworks 导出dwg 带映射模板 c# 图纸比例一比一导出
c#·solidworks
铁头七娃2 个月前
Solidworks 2024 根据机器人装配体,生成urdf文件
机器人·ros·solidworks
ddsoft1233 个月前
在装配拆卸指导动画中如何制作螺栓批量旋出的逼真视频
composer·软件·solidworks
zzzhpzhpzzz3 个月前
从SolidWorks中导出机器人URDF模型
机器人·ros·urdf·solidworks