惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Microsoft Azure Blog
Microsoft Azure Blog
量子位
小众软件
小众软件
C
Cybersecurity and Infrastructure Security Agency CISA
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
T
Tenable Blog
V
Vulnerabilities – Threatpost
Know Your Adversary
Know Your Adversary
T
Threat Research - Cisco Blogs
Latest news
Latest news
Spread Privacy
Spread Privacy
C
Cyber Attacks, Cyber Crime and Cyber Security
NISL@THU
NISL@THU
T
Tor Project blog
Hacker News: Ask HN
Hacker News: Ask HN
V2EX - 技术
V2EX - 技术
T
The Exploit Database - CXSecurity.com
博客园 - 三生石上(FineUI控件)
K
Kaspersky official blog
Cyberwarzone
Cyberwarzone
博客园 - 叶小钗
博客园 - 聂微东
Last Week in AI
Last Week in AI
爱范儿
爱范儿
腾讯CDC
博客园 - Franky
美团技术团队
J
Java Code Geeks
O
OpenAI News
L
Lohrmann on Cybersecurity
Simon Willison's Weblog
Simon Willison's Weblog
有赞技术团队
有赞技术团队
T
Threatpost
G
GRAHAM CLULEY
Hugging Face - Blog
Hugging Face - Blog
博客园 - 【当耐特】
宝玉的分享
宝玉的分享
I
Intezer
N
News and Events Feed by Topic
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
T
The Blog of Author Tim Ferriss
S
Security @ Cisco Blogs
Forbes - Security
Forbes - Security
N
News | PayPal Newsroom
Stack Overflow Blog
Stack Overflow Blog
Scott Helme
Scott Helme
H
Hacker News: Front Page
Cloudbric
Cloudbric

博客园 - 我才是银古

第16章:常见问题、排错与最佳实践 第15章:扩展生态、MCAD 与外部集成 第12章:实战案例:机械结构与 3D 打印零件 第14章:构建、测试、调试与贡献流程 第13章:OpenSCAD 源码架构与核心执行流程 第11章:预览、渲染、网格精度与性能优化 第09章:列表推导、递归与算法建模 第08章:参数化零件库与复用设计 第10章:导入导出、命令行与自动化 第06章:CSG 布尔建模方法 第07章:二维图形、拉伸、旋转与投影 第05章:基础几何、坐标系与变换 第04章:参数、变量、函数、模块与作用域 OpenSCAD 教程目录 第03章:OpenSCAD 语言基础 第02章:安装、环境配置与开发工作流 第01章:OpenSCAD 项目全景与学习路线 第02章:源码获取、编译与开发环境配置 第01章:OCCT项目全景与学习路线 第18章:二次开发实战与综合案例 第18章:综合实战案例 第17章:数据交换与协同 第16章:源码架构与二次开发 第15章:插件与自定义工作台开发 第14章:Python脚本宏与自动化 第13章:FEM仿真分析 第12章:CAM数控加工 第11章:SurfaceMesh与逆向工程 第10章:Draft二维绘图与BIM建筑 第09章:工程图TechDraw 第07章:参数化表达式与Spreadsheet 第08章:装配设计Assembly 第06章:Part工作台与几何内核 第05章:PartDesign实体特征建模 第04章:草图Sketcher约束建模 第02章:安装版本与工作环境配置 第03章:界面工作台与基础操作 第01章:项目全景与学习路线 第十二章:插件开发、研究功能与最佳实践 第十章:定时任务与自动化(Cron) 第七章:技能、记忆与自学习闭环 第八章:MCP 集成与上下文文件 第六章:工具系统与终端后端 第五章:模型供应商与配置体系 Hermes Agent 教程目录 第十一章:语音、视觉、浏览器与子代理协作 第四章:CLI/TUI 与会话管理 第十二章:学习路线、实战方案与最佳实践 第十一章:源码结构、开发调试与插件开发 第十章:自动化、远程访问、日志与排障 第九章:Control UI、节点、Canvas 与语音能力 第七章:工具、技能、插件与能力扩展 第八章:安全模型、访问控制与沙箱实践 第六章:Agent 工作区、会话与多智能体路由 第五章:多通道消息接入与聊天平台配置 第四章:配置体系、模型接入与认证管理 第三章:Gateway 架构、协议与运行机制 第二章:安装、环境准备与快速上手 第一章:OpenClaw 项目概览与核心定位 oh-my-openagent 教程目录 09-命令模型回退与配置参考 10-实战案例最佳实践与故障排除 05-工作模式-Ultrawork-Prometheus-Atlas 08-Hooks与MCP系统 06-Category与Skill系统 07-核心工具链 04-智能体全景详解 03-安装与环境配置 02-整体架构与多模型编排机制 01-项目简介与核心理念 01-项目概览与学习路线 02-安装部署与工具适配 03-Skill机制与using-superpowers 05-TDD系统化调试与完成前验证 04-需求澄清方案设计与计划编写 07-并行智能体子智能体与Git-Worktree 第六章:代码审查、反馈处理与分支收尾 08-中国特色Skills与本土团队落地 09-MCP构建工作流执行与自定义Skill 第23章:FreeCAD-Python-API Clipper2 C# 源码解读教程 第19章:PolyTree 多边形树结构 第20章:实际应用与最佳实践 第18章:Minkowski 和与差 第17章:RectClip 矩形裁剪优化 第16章:ClipperOffset 偏移类详解 第15章:填充规则详解 第14章:布尔运算执行流程 第13章:ClipperD 浮点裁剪类 第11章:OutRec 与 OutPt 输出结构 第9章:Active 活动边结构 第10章:Vertex 顶点与 LocalMinima 局部极小值 第12章:Clipper64 裁剪类详解 第7章:高精度运算与128位整数 第8章:ClipperBase 基类详解 第5章:枚举类型与常量定义 第6章:InternalClipper 内部工具类 第2章:核心数据结构 - Point64、PointD 第3章:路径与多边形表示 - Path64、PathD、Paths64、PathsD 第4章:矩形边界 - Rect64、RectD
二维绘图与视口管理
我才是银古 · 2026-06-19 · via 博客园 - 我才是银古

第十章:二维绘图与视口管理

10.1 绘图模块概述

10.1.1 LightCAD.Drawing的定位

LightCAD.Drawing是CAD系统中负责二维绘图和视口管理的核心模块。它位于RenderUtils之上、Runtime之下,处理所有与视口显示、用户输入和对象交互相关的功能。

10.1.2 模块结构

LightCAD.Drawing/
├── InputSys/                # 输入系统
│   ├── Inputer.cs           # 统一输入处理器
│   ├── MouseInput.cs        # 鼠标输入处理
│   └── KeyInput.cs          # 键盘输入处理
├── Snap/                    # 捕捉系统
│   ├── SnapManager.cs       # 捕捉管理器
│   ├── SnapPoint.cs         # 捕捉点定义
│   ├── OSnapTypes.cs        # 对象捕捉类型
│   └── GridSnap.cs          # 栅格捕捉
├── PViewPort/               # 视口组件
│   ├── ViewportRenderer.cs  # 视口渲染器
│   ├── ViewportManager.cs   # 视口管理器
│   ├── PanZoomController.cs # 平移缩放控制
│   └── ViewportConfig.cs    # 视口配置
└── ViewPortRtAction/        # 视口运行时动作
    ├── DrawAction.cs        # 绘图动作
    ├── SelectAction.cs      # 选择动作
    ├── EditAction.cs        # 编辑动作
    └── MeasureAction.cs     # 测量动作

10.2 视口系统

10.2.1 视口管理器

public class ViewportManager
{
    private List<Viewport> viewports = new();
    private Viewport activeViewport;

    /// <summary>
    /// 创建新视口
    /// </summary>
    public Viewport CreateViewport(ViewportConfig config)
    {
        var viewport = new Viewport
        {
            Id = GenerateViewportId(),
            Bounds = config.Bounds,
            Camera = CreateCamera(config),
            BackgroundColor = config.BackgroundColor
        };

        viewports.Add(viewport);
        return viewport;
    }

    /// <summary>
    /// 设置活动视口
    /// </summary>
    public void SetActiveViewport(Viewport viewport)
    {
        activeViewport?.OnDeactivated();
        activeViewport = viewport;
        activeViewport.OnActivated();
    }

    /// <summary>
    /// 获取鼠标位置对应的视口
    /// </summary>
    public Viewport GetViewportAtPoint(int screenX, int screenY)
    {
        return viewports.FirstOrDefault(v =>
            v.ContainsScreenPoint(screenX, screenY));
    }
}

public class Viewport
{
    public int Id { get; set; }
    public Rectangle Bounds { get; set; }
    public Camera Camera { get; set; }
    public LcColor BackgroundColor { get; set; }

    // 坐标转换
    public Point2d ScreenToWorld(int screenX, int screenY)
    {
        var normalizedX = (double)(screenX - Bounds.X) / Bounds.Width;
        var normalizedY = 1.0 - (double)(screenY - Bounds.Y) / Bounds.Height;

        return new Point2d(
            Camera.Left + normalizedX * (Camera.Right - Camera.Left),
            Camera.Bottom + normalizedY * (Camera.Top - Camera.Bottom)
        );
    }

    public (int x, int y) WorldToScreen(Point2d worldPoint)
    {
        var normalizedX = (worldPoint.X - Camera.Left) /
            (Camera.Right - Camera.Left);
        var normalizedY = (worldPoint.Y - Camera.Bottom) /
            (Camera.Top - Camera.Bottom);

        return (
            Bounds.X + (int)(normalizedX * Bounds.Width),
            Bounds.Y + (int)((1 - normalizedY) * Bounds.Height)
        );
    }

    public bool ContainsScreenPoint(int x, int y)
    {
        return x >= Bounds.X && x < Bounds.X + Bounds.Width
            && y >= Bounds.Y && y < Bounds.Y + Bounds.Height;
    }
}

10.2.2 视口渲染器

public class ViewportRenderer
{
    private SceneManager sceneManager;
    private RenderMaterialManager materialManager;

    /// <summary>
    /// 渲染视口内容
    /// </summary>
    public void Render(Viewport viewport, LcDocument document)
    {
        // 1. 计算可见范围
        var visibleBounds = viewport.GetVisibleBounds();

        // 2. 收集可见实体
        var visibleEntities = document.Entities
            .FindInBounds(visibleBounds)
            .Where(e => e.IsVisible)
            .Where(e => IsLayerVisible(e.LayerName, document.Layers));

        // 3. 渲染网格(如果启用)
        if (viewport.ShowGrid)
        {
            RenderGrid(viewport);
        }

        // 4. 按图层排序渲染实体
        var sortedEntities = visibleEntities
            .OrderBy(e => GetLayerOrder(e.LayerName))
            .ThenBy(e => e.Handle);

        foreach (var entity in sortedEntities)
        {
            RenderEntity(viewport, entity);
        }

        // 5. 渲染选择集高亮
        RenderSelection(viewport);

        // 6. 渲染临时几何(橡皮筋等)
        RenderTemporaryGeometry(viewport);

        // 7. 渲染捕捉标记
        RenderSnapMarkers(viewport);

        // 8. 渲染光标
        RenderCursor(viewport);
    }

    /// <summary>
    /// 渲染单个实体
    /// </summary>
    private void RenderEntity(Viewport viewport, LcEntity entity)
    {
        var color = ResolveColor(entity);
        var lineType = ResolveLineType(entity);
        var lineWeight = ResolveLineWeight(entity);

        switch (entity)
        {
            case LcLine line:
                DrawLine(viewport, line, color, lineWeight);
                break;
            case LcCircle circle:
                DrawCircle(viewport, circle, color, lineWeight);
                break;
            case LcArc arc:
                DrawArc(viewport, arc, color, lineWeight);
                break;
            case LcPolyline polyline:
                DrawPolyline(viewport, polyline, color, lineWeight);
                break;
            case LcText text:
                DrawText(viewport, text, color);
                break;
            case LcSolid3d solid:
                DrawSolid(viewport, solid);
                break;
        }
    }

    /// <summary>
    /// 渲染网格
    /// </summary>
    private void RenderGrid(Viewport viewport)
    {
        var gridSpacing = CalculateGridSpacing(viewport.ZoomLevel);
        var bounds = viewport.GetVisibleBounds();

        var startX = Math.Floor(bounds.Min.X / gridSpacing) * gridSpacing;
        var startY = Math.Floor(bounds.Min.Y / gridSpacing) * gridSpacing;

        var gridColor = new LcColor(200, 200, 200);
        var axisColor = new LcColor(150, 150, 150);

        // 绘制网格线
        for (var x = startX; x <= bounds.Max.X; x += gridSpacing)
        {
            var color = Math.Abs(x) < 1e-10 ? axisColor : gridColor;
            DrawScreenLine(viewport,
                new Point2d(x, bounds.Min.Y),
                new Point2d(x, bounds.Max.Y),
                color, 1);
        }

        for (var y = startY; y <= bounds.Max.Y; y += gridSpacing)
        {
            var color = Math.Abs(y) < 1e-10 ? axisColor : gridColor;
            DrawScreenLine(viewport,
                new Point2d(bounds.Min.X, y),
                new Point2d(bounds.Max.X, y),
                color, 1);
        }
    }
}

10.3 平移与缩放

10.3.1 平移缩放控制器

public class PanZoomController
{
    private Viewport viewport;
    private bool isPanning = false;
    private Point2d panStartWorld;
    private int panStartScreenX, panStartScreenY;

    /// <summary>
    /// 缩放级别
    /// </summary>
    public double ZoomLevel { get; private set; } = 1.0;

    /// <summary>
    /// 鼠标滚轮缩放
    /// </summary>
    public void ZoomAtPoint(int screenX, int screenY, double zoomFactor)
    {
        // 获取缩放前鼠标位置的世界坐标
        var worldPoint = viewport.ScreenToWorld(screenX, screenY);

        // 计算新的缩放级别
        var newZoom = ZoomLevel * zoomFactor;
        newZoom = Math.Max(0.001, Math.Min(100000, newZoom));

        // 调整相机使得缩放中心点不移动
        var ratio = ZoomLevel / newZoom;
        var cameraCenter = viewport.Camera.Center;

        viewport.Camera.Center = new Point2d(
            worldPoint.X + (cameraCenter.X - worldPoint.X) * ratio,
            worldPoint.Y + (cameraCenter.Y - worldPoint.Y) * ratio
        );

        ZoomLevel = newZoom;
        viewport.Camera.UpdateExtents(newZoom);
    }

    /// <summary>
    /// 缩放到全部(显示所有实体)
    /// </summary>
    public void ZoomExtents(BoundingBox2d entityBounds)
    {
        var padding = 0.1; // 10%的边距
        var width = entityBounds.Width * (1 + padding);
        var height = entityBounds.Height * (1 + padding);

        var viewAspect = (double)viewport.Bounds.Width / viewport.Bounds.Height;
        var entityAspect = width / height;

        if (entityAspect > viewAspect)
        {
            // 以宽度为准
            ZoomLevel = viewport.Bounds.Width / width;
        }
        else
        {
            // 以高度为准
            ZoomLevel = viewport.Bounds.Height / height;
        }

        viewport.Camera.Center = entityBounds.Center;
        viewport.Camera.UpdateExtents(ZoomLevel);
    }

    /// <summary>
    /// 开始平移
    /// </summary>
    public void BeginPan(int screenX, int screenY)
    {
        isPanning = true;
        panStartScreenX = screenX;
        panStartScreenY = screenY;
        panStartWorld = viewport.ScreenToWorld(screenX, screenY);
    }

    /// <summary>
    /// 执行平移
    /// </summary>
    public void DoPan(int screenX, int screenY)
    {
        if (!isPanning) return;

        var currentWorld = viewport.ScreenToWorld(screenX, screenY);
        var delta = panStartWorld - currentWorld;

        viewport.Camera.Center = new Point2d(
            viewport.Camera.Center.X + delta.X,
            viewport.Camera.Center.Y + delta.Y
        );
    }

    /// <summary>
    /// 结束平移
    /// </summary>
    public void EndPan()
    {
        isPanning = false;
    }
}

10.4 选择系统

10.4.1 选择管理器

public class SelectionManager
{
    private HashSet<LcEntity> selectedEntities = new();

    /// <summary>
    /// 选择集变更事件
    /// </summary>
    public event EventHandler<SelectionChangedArgs> SelectionChanged;

    /// <summary>
    /// 单击选择
    /// </summary>
    public void PointSelect(Point2d worldPoint, double tolerance,
                            LcDocument document, bool addToSelection = false)
    {
        if (!addToSelection)
        {
            ClearSelection();
        }

        // 查找点击位置附近的实体
        var hitEntity = document.Entities
            .Where(e => e.IsVisible)
            .OrderBy(e => e.DistanceTo(worldPoint))
            .FirstOrDefault(e => e.DistanceTo(worldPoint) <= tolerance);

        if (hitEntity != null)
        {
            ToggleSelect(hitEntity);
        }
    }

    /// <summary>
    /// 窗口选择(从左到右拖动)
    /// </summary>
    public void WindowSelect(BoundingBox2d window, LcDocument document,
                             bool addToSelection = false)
    {
        if (!addToSelection)
        {
            ClearSelection();
        }

        foreach (var entity in document.Entities)
        {
            if (entity.IsVisible &&
                window.Contains(entity.Bounds.Min) &&
                window.Contains(entity.Bounds.Max))
            {
                Select(entity);
            }
        }
    }

    /// <summary>
    /// 交叉选择(从右到左拖动)
    /// </summary>
    public void CrossingSelect(BoundingBox2d window, LcDocument document,
                               bool addToSelection = false)
    {
        if (!addToSelection)
        {
            ClearSelection();
        }

        foreach (var entity in document.Entities)
        {
            if (entity.IsVisible &&
                entity.Bounds.Intersects(window))
            {
                Select(entity);
            }
        }
    }

    public void Select(LcEntity entity)
    {
        if (selectedEntities.Add(entity))
        {
            SelectionChanged?.Invoke(this,
                new SelectionChangedArgs(entity, true));
        }
    }

    public void Deselect(LcEntity entity)
    {
        if (selectedEntities.Remove(entity))
        {
            SelectionChanged?.Invoke(this,
                new SelectionChangedArgs(entity, false));
        }
    }

    public void ClearSelection()
    {
        var previous = selectedEntities.ToList();
        selectedEntities.Clear();
        foreach (var entity in previous)
        {
            SelectionChanged?.Invoke(this,
                new SelectionChangedArgs(entity, false));
        }
    }

    public IReadOnlyCollection<LcEntity> SelectedEntities => selectedEntities;
    public int Count => selectedEntities.Count;
    public bool IsSelected(LcEntity entity) => selectedEntities.Contains(entity);
}

10.5 夹点编辑

10.5.1 夹点系统

夹点(Grip)是实体上的可拖动控制点,允许用户通过直接拖动来编辑实体的几何形状:

public class GripPoint
{
    public Point2d Position { get; set; }
    public GripType Type { get; set; }
    public LcEntity Owner { get; set; }
    public int Index { get; set; }
    public Action<Point2d> OnDrag { get; set; }
}

public enum GripType
{
    Vertex,     // 顶点夹点
    MidPoint,   // 中点夹点
    Center,     // 中心夹点
    Quadrant,   // 象限点夹点
    Control     // 控制点夹点
}

public class GripManager
{
    private List<GripPoint> activeGrips = new();
    private GripPoint draggedGrip = null;

    /// <summary>
    /// 为实体生成夹点
    /// </summary>
    public List<GripPoint> GetGrips(LcEntity entity)
    {
        return entity switch
        {
            LcLine line => GetLineGrips(line),
            LcCircle circle => GetCircleGrips(circle),
            LcArc arc => GetArcGrips(arc),
            LcPolyline polyline => GetPolylineGrips(polyline),
            _ => new List<GripPoint>()
        };
    }

    private List<GripPoint> GetLineGrips(LcLine line)
    {
        return new List<GripPoint>
        {
            new GripPoint
            {
                Position = line.StartPoint,
                Type = GripType.Vertex,
                Owner = line,
                Index = 0,
                OnDrag = p => line.StartPoint = p
            },
            new GripPoint
            {
                Position = line.EndPoint,
                Type = GripType.Vertex,
                Owner = line,
                Index = 1,
                OnDrag = p => line.EndPoint = p
            },
            new GripPoint
            {
                Position = line.MidPoint,
                Type = GripType.MidPoint,
                Owner = line,
                Index = 2,
                OnDrag = p =>
                {
                    var offset = p - line.MidPoint;
                    line.StartPoint = line.StartPoint + offset;
                    line.EndPoint = line.EndPoint + offset;
                }
            }
        };
    }

    private List<GripPoint> GetCircleGrips(LcCircle circle)
    {
        return new List<GripPoint>
        {
            new GripPoint
            {
                Position = circle.Center,
                Type = GripType.Center,
                Owner = circle,
                OnDrag = p => circle.Center = p
            },
            new GripPoint
            {
                Position = circle.PointAt(0),
                Type = GripType.Quadrant,
                Owner = circle,
                OnDrag = p =>
                    circle.Radius = circle.Center.DistanceTo(p)
            },
            new GripPoint
            {
                Position = circle.PointAt(Math.PI / 2),
                Type = GripType.Quadrant,
                Owner = circle,
                OnDrag = p =>
                    circle.Radius = circle.Center.DistanceTo(p)
            },
            new GripPoint
            {
                Position = circle.PointAt(Math.PI),
                Type = GripType.Quadrant,
                Owner = circle,
                OnDrag = p =>
                    circle.Radius = circle.Center.DistanceTo(p)
            },
            new GripPoint
            {
                Position = circle.PointAt(3 * Math.PI / 2),
                Type = GripType.Quadrant,
                Owner = circle,
                OnDrag = p =>
                    circle.Radius = circle.Center.DistanceTo(p)
            }
        };
    }
}

10.6 线型系统

10.6.1 线型定义

public class LineTypeDefinition
{
    public string Name { get; set; }
    public string Description { get; set; }
    public double[] Pattern { get; set; }

    // 预定义线型
    public static readonly LineTypeDefinition Continuous =
        new LineTypeDefinition
        {
            Name = "Continuous",
            Description = "实线",
            Pattern = Array.Empty<double>()
        };

    public static readonly LineTypeDefinition Dashed =
        new LineTypeDefinition
        {
            Name = "Dashed",
            Description = "虚线",
            Pattern = new[] { 10.0, -5.0 } // 画10,空5
        };

    public static readonly LineTypeDefinition DashDot =
        new LineTypeDefinition
        {
            Name = "DashDot",
            Description = "点画线",
            Pattern = new[] { 10.0, -3.0, 1.0, -3.0 }
        };

    public static readonly LineTypeDefinition Center =
        new LineTypeDefinition
        {
            Name = "Center",
            Description = "中心线",
            Pattern = new[] { 20.0, -5.0, 5.0, -5.0 }
        };

    public static readonly LineTypeDefinition Hidden =
        new LineTypeDefinition
        {
            Name = "Hidden",
            Description = "隐藏线",
            Pattern = new[] { 5.0, -3.0 }
        };
}

10.7 橡皮筋绘制

10.7.1 临时图形系统

在绘制图元过程中,需要显示跟随鼠标移动的临时图形(橡皮筋效果):

public class RubberBand
{
    private List<TemporaryGraphic> temporaryGraphics = new();

    /// <summary>
    /// 添加临时直线
    /// </summary>
    public void AddLine(Point2d start, Point2d end)
    {
        temporaryGraphics.Add(new TemporaryGraphic
        {
            Type = TempGraphicType.Line,
            Points = new[] { start, end },
            Color = new LcColor(0, 150, 255)
        });
    }

    /// <summary>
    /// 添加临时圆
    /// </summary>
    public void AddCircle(Point2d center, double radius)
    {
        temporaryGraphics.Add(new TemporaryGraphic
        {
            Type = TempGraphicType.Circle,
            Center = center,
            Radius = radius,
            Color = new LcColor(0, 150, 255)
        });
    }

    /// <summary>
    /// 添加临时矩形
    /// </summary>
    public void AddRectangle(Point2d corner1, Point2d corner2)
    {
        temporaryGraphics.Add(new TemporaryGraphic
        {
            Type = TempGraphicType.Rectangle,
            Points = new[] { corner1, corner2 },
            Color = new LcColor(0, 150, 255)
        });
    }

    /// <summary>
    /// 清除所有临时图形
    /// </summary>
    public void Clear()
    {
        temporaryGraphics.Clear();
    }

    /// <summary>
    /// 渲染临时图形
    /// </summary>
    public void Render(ViewportRenderer renderer)
    {
        foreach (var graphic in temporaryGraphics)
        {
            renderer.DrawTemporary(graphic);
        }
    }
}

10.8 本章小结

本章详细介绍了LightCAD的二维绘图和视口管理系统。视口系统提供了坐标转换、平移缩放、选择管理等核心功能。夹点编辑系统允许用户通过拖拽控制点直接编辑图元。线型系统支持实线、虚线、点画线等多种线型。橡皮筋绘制为交互式绘图提供了实时视觉反馈。这些系统共同构成了LightCAD二维绘图交互的基础。


上一章第九章:渲染系统与Three.js集成

下一章第十一章:视图构建系统