Browse Source

初始化项目

bill 4 years ago
parent
commit
bc49cbe4ec
100 changed files with 72998 additions and 0 deletions
  1. 2 0
      .gitignore
  2. 1 0
      dist/bundle.js
  3. 157 0
      dist/index.html
  4. 1 0
      dist/static/data.js
  5. 275 0
      dist/static/data.json
  6. BIN
      dist/static/images/cad_column.png
  7. BIN
      dist/static/images/cad_door.png
  8. BIN
      dist/static/images/cad_window.png
  9. BIN
      dist/static/images/icon_biaochi_defult.png
  10. BIN
      dist/static/images/icon_biaochi_select.png
  11. BIN
      dist/static/images/icon_feiji.png
  12. BIN
      dist/static/images/shot.png
  13. 157 0
      dist/static/index.html
  14. 4 0
      dist/static/jquery-2.1.1.min.js
  15. 58 0
      dist/static/style/public.css
  16. 47365 0
      dist/static/three95.min.js
  17. 533 0
      lang/_en.ts
  18. 482 0
      lang/_fr.js
  19. 537 0
      lang/_zh.ts
  20. 13 0
      lang/index.ts
  21. 9112 0
      package-lock.json
  22. 48 0
      package.json
  23. 241 0
      src/CAD/core/additional/calcRoom.js
  24. 272 0
      src/CAD/core/additional/dataHandle.ts
  25. 53 0
      src/CAD/core/additional/disabled.ts
  26. 420 0
      src/CAD/core/additional/eleAttr.ts
  27. 348 0
      src/CAD/core/additional/insert.ts
  28. 135 0
      src/CAD/core/additional/label.ts
  29. 95 0
      src/CAD/core/additional/rote.ts
  30. 91 0
      src/CAD/core/additional/screenshot.ts
  31. 67 0
      src/CAD/core/additional/sign.ts
  32. 64 0
      src/CAD/core/additional/styleSet.ts
  33. 75 0
      src/CAD/core/additional/transform.ts
  34. 186 0
      src/CAD/core/architecture/bayCase.ts
  35. 157 0
      src/CAD/core/architecture/casement.ts
  36. 412 0
      src/CAD/core/architecture/column.ts
  37. BIN
      src/CAD/core/architecture/door/cad_enter@3x.png
  38. BIN
      src/CAD/core/architecture/door/cad_enter_dark@3x.png
  39. 188 0
      src/CAD/core/architecture/door/index.ts
  40. 123 0
      src/CAD/core/architecture/groundCase.ts
  41. 280 0
      src/CAD/core/architecture/linearch.ts
  42. 152 0
      src/CAD/core/architecture/slideDoor.ts
  43. 86 0
      src/CAD/core/base/cad.ts
  44. 553 0
      src/CAD/core/base/processing/bk.ts
  45. 203 0
      src/CAD/core/base/processing/index.ts
  46. 76 0
      src/CAD/core/base/processing/pointOper.ts
  47. 35 0
      src/CAD/core/base/processing/room.ts
  48. 187 0
      src/CAD/core/base/processing/toData.ts
  49. 349 0
      src/CAD/core/base/processing/toEle.ts
  50. 155 0
      src/CAD/core/base/renderer.ts
  51. 64 0
      src/CAD/core/base/stack.ts
  52. 344 0
      src/CAD/core/base/state.ts
  53. 69 0
      src/CAD/core/constant/Element.ts
  54. 253 0
      src/CAD/core/core/element.ts
  55. 51 0
      src/CAD/core/core/fixedline.ts
  56. 71 0
      src/CAD/core/core/fixedpoint.ts
  57. 53 0
      src/CAD/core/core/line.ts
  58. 37 0
      src/CAD/core/core/linepoint.ts
  59. 166 0
      src/CAD/core/core/point.ts
  60. 524 0
      src/CAD/core/core/wallfixedline.ts
  61. 447 0
      src/CAD/core/core/wallline.ts
  62. 36 0
      src/CAD/core/furniture/column.ts
  63. 68 0
      src/CAD/core/furniture/flue.ts
  64. 325 0
      src/CAD/core/furniture/furn.ts
  65. 712 0
      src/CAD/core/geometry.ts
  66. 43 0
      src/CAD/core/index.ts
  67. 179 0
      src/CAD/core/label/direction.ts
  68. 301 0
      src/CAD/core/label/gauge.ts
  69. BIN
      src/CAD/core/label/images/dire.png
  70. 1 0
      src/CAD/core/label/images/dire.ts
  71. BIN
      src/CAD/core/label/images/dire_dark.png
  72. 1 0
      src/CAD/core/label/images/dire_dark.ts
  73. BIN
      src/CAD/core/label/images/icon_feiji.png
  74. BIN
      src/CAD/core/label/images/pan.png
  75. 1 0
      src/CAD/core/label/images/pan.ts
  76. BIN
      src/CAD/core/label/images/rote-ts.png
  77. 279 0
      src/CAD/core/label/route.ts
  78. 97 0
      src/CAD/core/label/sign.ts
  79. 129 0
      src/CAD/core/label/tagging.ts
  80. 3 0
      src/CAD/core/style.css
  81. 103 0
      src/CAD/core/util.ts
  82. 335 0
      src/CAD/eleAttr.ts
  83. 7 0
      src/CAD/images.d.ts
  84. 94 0
      src/CAD/index.ts
  85. 29 0
      src/CAD/other.ts
  86. 290 0
      src/CAD/screenAdapt.ts
  87. 56 0
      src/CAD/toCanvas.ts
  88. 3298 0
      static/Mesh_floorplan(1).json
  89. 1 0
      static/data.js
  90. 646 0
      static/data.json
  91. BIN
      static/images/cad_column.png
  92. BIN
      static/images/cad_door.png
  93. BIN
      static/images/cad_window.png
  94. BIN
      static/images/icon_biaochi_defult.png
  95. BIN
      static/images/icon_biaochi_select.png
  96. BIN
      static/images/icon_feiji.png
  97. BIN
      static/images/shot.png
  98. 133 0
      static/index.html
  99. 4 0
      static/jquery-2.1.1.min.js
  100. 0 0
      static/style/public.css

+ 2 - 0
.gitignore

@@ -0,0 +1,2 @@
+.DS_Store
+node_modules

File diff suppressed because it is too large
+ 1 - 0
dist/bundle.js


+ 157 - 0
dist/index.html

@@ -0,0 +1,157 @@
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+  <meta charset="UTF-8">
+  <meta name="viewport" content="width=device-width,initial-scale=1">
+  <meta http-equiv="X-UA-Compatible" content="ie=edge">
+  <link rel="stylesheet" href="./static/style/public.css">
+  <title>4DCAD</title>
+</head>
+
+<body>
+  <div class="body">
+    <div id="cad"></div>
+    <div class="content">
+      <ul>
+        <li>
+          <div class="itemTitle">
+            <span>增添细节</span>
+          </div>
+          <ul class="chose" id="architecture">
+            <li attr-type="door">
+              <div><i class="iconfont icon_door"></i></div><span>门</span>
+            </li>
+            <li attr-type="casement">
+              <div><i class="iconfont icon_window"></i></div><span>窗户</span>
+            </li>
+            <li attr-type="column">
+              <div><i class="iconfont icon_column"></i></div><span>柱子</span>
+            </li>
+            <li attr-type="point">
+              <div><i class="iconfont icon_point"></i></div><span>点</span>
+            </li>
+            <li attr-type="slideDoor">
+              <div><i class="iconfont icon_point"></i></div><span>移门</span>
+            </li>
+            <li attr-type="tagging">
+              <div><i class="iconfont icon_point"></i></div><span>标注</span>
+            </li>
+            <li attr-type="groundCase">
+              <div><i class="iconfont icon_point"></i></div><span>落地窗</span>
+            </li>
+            <li attr-type="bayCase">
+              <div><i class="iconfont icon_point"></i></div><span>飘窗</span>
+            </li>
+            <li attr-type="furnColumn">
+              <div><i class="iconfont icon_column"></i></div><span>独立柱</span>
+            </li>
+            <li attr-type="furnFlue">
+              <div><i class="iconfont icon_column"></i></div><span>独立柱</span>
+            </li>
+            <li attr-type="line">
+              <div><i class="iconfont icon_column"></i></div><span>墙</span>
+            </li>
+          </ul>
+
+          <a id="aaa">导出</a>
+          <a id="bbb">测试</a>
+        </li>
+      </ul>
+
+      <a class="btn" id="resove">&lt;</a>
+      <a class="btn" id="back">&gt;</a>
+    </div>
+  </div>
+
+<script type="text/javascript" src="bundle.js"></script></body>
+
+</html>
+<script src="./static/three95.min.js"></script>
+<script src="./static/jquery-2.1.1.min.js"></script>
+<script>
+  //解析查询字符串
+  function getQueryStringArgs() {
+    //取得查询字符串,并去掉开头'?'
+    var qs = location.search.length ? location.search.substring(1) : '';
+    //保存数据的对象
+    var args = {},
+      //以分割符'&'分割字符串,并以数组形式返回
+      items = qs.length ? qs.split('&') : [],
+      item = null,
+      name = null,
+      value = null,
+      i = 0,
+      len = items.length;
+    //逐个将每一项添加到args对象中
+    for (; i < len; i++) {
+      item = items[i].split('=');
+      //解码操作,因为查询字符串经过编码的
+      name = decodeURIComponent(item[0]);
+      value = decodeURIComponent(item[1]);
+      value = item[1];
+      if (name.length) {
+        args[name] = value;
+      }
+    }
+    return args;
+  }
+
+  function main() {
+    let args = getQueryStringArgs()
+
+    $.ajax({
+      url: 'https://4dkk.4dage.com/data/data' + args.m + '/floor.json?m=36',
+      method: 'GET',
+      success(data) {
+        let $layer = document.querySelector('#cad')
+        let cad = structureCAD({
+          data:
+          {
+            block: [],
+            column: [],
+            door: [],
+            hole: [],
+            segment: [],
+            "vertex-xy": [],
+            "vertex-z": []
+          }, layer: $layer
+        });
+
+        cad.showLabel();
+        cad.showDire();
+        cad.loadData(JSON.parse(data));
+
+        [].forEach.call(document.querySelectorAll('#architecture  li'), function ($item) {
+          $item.addEventListener('click', function (ev) {
+            $layer.addEventListener('click', function handle(ev) {
+              let ret = cad.increase($item.getAttribute('attr-type'), { x: ev.offsetX, y: ev.offsetY }, null, null, true)
+
+              if (ret.error) {
+                alert(ret.msg)
+              }
+              this.removeEventListener('click', handle)
+            })
+          })
+        })
+
+        document.querySelector('#resove').addEventListener('click', function (ev) {
+          cad.previous()
+        })
+        document.querySelector('#back').addEventListener('click', function (ev) {
+          cad.next()
+        })
+        document.querySelector('#aaa').addEventListener('click', function (ev) {
+          cad.screenshot()
+            .then(function (data) {
+              console.log(data)
+              window.open(window.URL.createObjectURL(data.file))
+            })
+
+        })
+      }
+    })
+  }
+
+  main()
+</script>

File diff suppressed because it is too large
+ 1 - 0
dist/static/data.js


+ 275 - 0
dist/static/data.json

@@ -0,0 +1,275 @@
+{
+  "vertex-xy": [
+    {
+      "id": 28,
+      "x": 2.72,
+      "y": -0.19
+    },
+    {
+      "id": 34,
+      "x": 3.85,
+      "y": -0.19
+    },
+    {
+      "id": 87,
+      "x": 3.85,
+      "y": 0.91
+    },
+    {
+      "id": 75,
+      "x": 2.72,
+      "y": -3.58
+    },
+    {
+      "id": 35,
+      "x": 6.44,
+      "y": -3.58
+    },
+    {
+      "id": 9,
+      "x": 6.44,
+      "y": 0.96
+    },
+    {
+      "id": 76,
+      "x": 4.56,
+      "y": 0.96
+    },
+    {
+      "id": 88,
+      "x": 4.56,
+      "y": 0.91
+    }
+  ],
+  "vertex-z": [
+    {
+      "id": 1,
+      "z": 1.34
+    },
+    {
+      "id": 2,
+      "z": -1.55
+    }
+  ],
+  "segment": [
+    {
+      "a": 34,
+      "b": 28,
+      "id": 1
+    },
+    {
+      "a": 28,
+      "b": 75,
+      "id": 2
+    },
+    {
+      "a": 75,
+      "b": 35,
+      "id": 3
+    },
+    {
+      "a": 35,
+      "b": 9,
+      "id": 4
+    },
+    {
+      "a": 9,
+      "b": 76,
+      "id": 5
+    },
+    {
+      "a": 76,
+      "b": 88,
+      "id": 6
+    },
+    {
+      "a": 88,
+      "b": 87,
+      "id": 7
+    },
+    {
+      "a": 87,
+      "b": 34,
+      "id": 8
+    }
+  ],
+  "block": [
+    {
+      "wall": [
+        1,
+        2,
+        3,
+        4,
+        5,
+        6,
+        7,
+        8
+      ],
+      "floor": [],
+      "height": 2.89,
+      "ground": [
+        34,
+        28,
+        75,
+        35,
+        9,
+        76,
+        88,
+        87
+      ],
+      "area": 15.553800000000003,
+      "top": 1,
+      "bottom": 2
+    }
+  ],
+  "hole": [
+    {
+      "plane": {
+        "x": 0,
+        "y": 1,
+        "z": 0,
+        "w": 0.19
+      },
+      "phase": {
+        "lb": 0,
+        "rb": 0
+      },
+      "height": 2.2109729995762004,
+      "wall": [],
+      "point": [
+        {
+          "x": 3.72,
+          "y": -0.19,
+          "z": -1.44
+        },
+        {
+          "x": 2.89,
+          "y": -0.19,
+          "z": -1.44
+        },
+        {
+          "x": 3.72,
+          "y": -0.19,
+          "z": 0.7709729995762006
+        },
+        {
+          "x": 2.89,
+          "y": -0.19,
+          "z": 0.7709729995762006
+        }
+      ]
+    },
+    {
+      "plane": {
+        "x": 0,
+        "y": 1,
+        "z": 0,
+        "w": -0.96
+      },
+      "phase": {
+        "lb": 0,
+        "rb": 0
+      },
+      "height": 2,
+      "wall": [],
+      "point": [
+        {
+          "x": 5.88,
+          "y": 0.96,
+          "z": -1.44
+        },
+        {
+          "x": 5.09,
+          "y": 0.96,
+          "z": -1.44
+        },
+        {
+          "x": 5.88,
+          "y": 0.96,
+          "z": 0.56
+        },
+        {
+          "x": 5.09,
+          "y": 0.96,
+          "z": 0.56
+        }
+      ]
+    }
+  ],
+  "window": [
+    {
+      "line": 3,
+      "pos": [
+        6.04,
+        -3.58,
+        2.93,
+        -3.58
+      ],
+      "top": 0.644082252244718,
+      "bottom": -0.8997335080699127
+    }
+  ],
+  "column": [
+    {
+      "line": 4,
+      "pos": [
+        6.44,
+        -0.65,
+        6.44,
+        0.96,
+        5.88,
+        -0.65,
+        5.88,
+        0.96
+      ]
+    },
+    {
+      "line": 7,
+      "pos": [
+        4.56,
+        0.91,
+        3.85,
+        0.91,
+        4.56,
+        -0.68,
+        3.85,
+        -0.68
+      ]
+    }
+  ],
+  "door": [
+    {
+      "line": 1,
+      "pos": [
+        3.72,
+        -0.19,
+        2.89,
+        -0.19
+      ],
+      "top": 0.7709729995762006,
+      "bottom": -1.44,
+      "within": 0
+    },
+    {
+      "line": 5,
+      "pos": [
+        5.88,
+        0.96,
+        5.09,
+        0.96
+      ],
+      "top": 0.56,
+      "bottom": -1.44,
+      "within": 0
+    }
+  ],
+  "openSpace": [],
+  "slideDoor": [],
+  "surplus": [],
+  "tagging": [],
+  "groundCase": [],
+  "bayCase": [],
+  "furnColumn": [],
+  "furnFlue": [],
+  "dire": 0
+}

BIN
dist/static/images/cad_column.png


BIN
dist/static/images/cad_door.png


BIN
dist/static/images/cad_window.png


BIN
dist/static/images/icon_biaochi_defult.png


BIN
dist/static/images/icon_biaochi_select.png


BIN
dist/static/images/icon_feiji.png


BIN
dist/static/images/shot.png


+ 157 - 0
dist/static/index.html

@@ -0,0 +1,157 @@
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+  <meta charset="UTF-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <meta http-equiv="X-UA-Compatible" content="ie=edge">
+  <link rel="stylesheet" href="./static/style/public.css">
+  <title>4DCAD</title>
+</head>
+
+<body>
+  <div class="body">
+    <div id="cad"></div>
+    <div class="content">
+      <ul>
+        <li>
+          <div class="itemTitle">
+            <span>增添细节</span>
+          </div>
+          <ul class="chose" id="architecture">
+            <li attr-type="door">
+              <div><i class="iconfont icon_door"></i></div><span>门</span>
+            </li>
+            <li attr-type="casement">
+              <div><i class="iconfont icon_window"></i></div><span>窗户</span>
+            </li>
+            <li attr-type="column">
+              <div><i class="iconfont icon_column"></i></div><span>柱子</span>
+            </li>
+            <li attr-type="point">
+              <div><i class="iconfont icon_point"></i></div><span>点</span>
+            </li>
+            <li attr-type="slideDoor">
+              <div><i class="iconfont icon_point"></i></div><span>移门</span>
+            </li>
+            <li attr-type="tagging">
+              <div><i class="iconfont icon_point"></i></div><span>标注</span>
+            </li>
+            <li attr-type="groundCase">
+              <div><i class="iconfont icon_point"></i></div><span>落地窗</span>
+            </li>
+            <li attr-type="bayCase">
+              <div><i class="iconfont icon_point"></i></div><span>飘窗</span>
+            </li>
+            <li attr-type="furnColumn">
+              <div><i class="iconfont icon_column"></i></div><span>独立柱</span>
+            </li>
+            <li attr-type="furnFlue">
+              <div><i class="iconfont icon_column"></i></div><span>独立柱</span>
+            </li>
+            <li attr-type="line">
+              <div><i class="iconfont icon_column"></i></div><span>墙</span>
+            </li>
+          </ul>
+
+          <a id="aaa">导出</a>
+          <a id="bbb">测试</a>
+        </li>
+      </ul>
+
+      <a class="btn" id="resove">&lt;</a>
+      <a class="btn" id="back">&gt;</a>
+    </div>
+  </div>
+
+</body>
+
+</html>
+<script src="./static/three95.min.js"></script>
+<script src="./static/jquery-2.1.1.min.js"></script>
+<script>
+  //解析查询字符串
+  function getQueryStringArgs() {
+    //取得查询字符串,并去掉开头'?'
+    var qs = location.search.length ? location.search.substring(1) : '';
+    //保存数据的对象
+    var args = {},
+      //以分割符'&'分割字符串,并以数组形式返回
+      items = qs.length ? qs.split('&') : [],
+      item = null,
+      name = null,
+      value = null,
+      i = 0,
+      len = items.length;
+    //逐个将每一项添加到args对象中
+    for (; i < len; i++) {
+      item = items[i].split('=');
+      //解码操作,因为查询字符串经过编码的
+      name = decodeURIComponent(item[0]);
+      value = decodeURIComponent(item[1]);
+      value = item[1];
+      if (name.length) {
+        args[name] = value;
+      }
+    }
+    return args;
+  }
+
+  function main() {
+    let args = getQueryStringArgs()
+
+    $.ajax({
+      url: 'https://4dkk.4dage.com/data/data' + args.m + '/floor.json?m=36',
+      method: 'GET',
+      success(data) {
+        let $layer = document.querySelector('#cad')
+        let cad = structureCAD({
+          data:
+          {
+            block: [],
+            column: [],
+            door: [],
+            hole: [],
+            segment: [],
+            "vertex-xy": [],
+            "vertex-z": []
+          }, layer: $layer
+        });
+
+        cad.showLabel();
+        cad.showDire();
+        cad.loadData(JSON.parse(data));
+
+        [].forEach.call(document.querySelectorAll('#architecture  li'), function ($item) {
+          $item.addEventListener('click', function (ev) {
+            $layer.addEventListener('click', function handle(ev) {
+              let ret = cad.increase($item.getAttribute('attr-type'), { x: ev.offsetX, y: ev.offsetY }, null, null, true)
+
+              if (ret.error) {
+                alert(ret.msg)
+              }
+              this.removeEventListener('click', handle)
+            })
+          })
+        })
+
+        document.querySelector('#resove').addEventListener('click', function (ev) {
+          cad.previous()
+        })
+        document.querySelector('#back').addEventListener('click', function (ev) {
+          cad.next()
+        })
+        document.querySelector('#aaa').addEventListener('click', function (ev) {
+          cad.screenshot()
+            .then(function (data) {
+              console.log(data)
+              window.open(window.URL.createObjectURL(data.file))
+            })
+
+        })
+      }
+    })
+  }
+
+  main()
+</script>

File diff suppressed because it is too large
+ 4 - 0
dist/static/jquery-2.1.1.min.js


+ 58 - 0
dist/static/style/public.css

@@ -0,0 +1,58 @@
+* {
+  margin: 0;
+  padding: 0;
+}
+
+html {
+  --width: 100%;
+  --height: 100%;
+}
+html, body, .body {
+  width: var(--width);
+  height: var(--height);
+}
+
+
+.body {
+  display: flex;
+  overflow: hidden;
+}
+
+#cad {
+  flex: 1;
+  background-color: #000;
+}
+
+.variable {
+  cursor: pointer;
+}
+.content {
+  /* position: absolute;
+  right: 0;
+  top: 0;
+  bottom: 0; */
+  background-color: #fff;
+}
+
+i {
+  display: inline-block;
+  width: 50px;
+  height: 50px;
+  border: 1px solid red;
+}
+
+.chose li {
+  text-align: center;
+}
+
+.btn {
+  display: inline-block;
+  width: 45%;
+  height: 30px;
+  border-radius: 5px;
+  background-color: #181c1d;
+  color: #fff;
+  text-align: center;
+  cursor: pointer;
+  line-height: 30px;
+}

File diff suppressed because it is too large
+ 47365 - 0
dist/static/three95.min.js


+ 533 - 0
lang/_en.ts

@@ -0,0 +1,533 @@
+module.exports = {
+    "menu": {
+        "__name": "菜单",
+        "music": "BGM",
+        "base": "Basic Setting",
+        "information": "Scene Info",
+        "screen": "Scene Cover",
+        "hotspot": "Add Hotspot",
+        "guide": "Tour Guide",
+        "sign": "Spot Logo",
+        "walk": "Roaming Possibility",
+        "model": "Model Edit",
+        "custom": "Upload Download",
+        "videos": "Add Videos",
+        "vrhouse": "VR house",
+        "business": "Business part",
+        "scene": "Scene transition",
+        "video": "Add video",
+        "decor": "Decor",
+        "link": "Scene associate"
+    },
+    "modules": {
+        "__name": "模块",
+        "base": {
+            "__name": "基础设置",
+            "qrcode": "QR Code",
+            "qrcode_download": "Download the QR code",
+            "qrcode_tips": "Customize your logo",
+            "scene_link": "Scene Link",
+            "scene_link_copy": "Copy the link",
+            "scene_link_copy_tips": "Copied",
+            "bgm": "BGM",
+            "pano_text": "Roaming",
+            "mode_2d_text": "Layout Plan",
+            "mode_3d_text": "3D Model",
+            "map_text": "Mini-map",
+            "vr_text": "VR Mode",
+            "vr_tips": "Please view the VR effect on the mobile display page",
+            "guide_text": "Tour Guide",
+            "rule_text": "Measurements",
+            "cad_text": "Floor Layout",
+            "measure_text": "Ruler",
+            "measure_tips": "The measuring function cannot be used in edit mode, please use it on display page",
+            "turned_vr": "VR turned {status}",
+            "turned_map": "Mini-map feature  {status}",
+            "turned_cad": "Layout plan {status}",
+            "turned_m2d": "Plane view {status}",
+            "turned_m3d": "3D view {status}",
+            "turned_pano": "Roaming view {status}",
+            "turned_rule": "Measuring function {status}",
+            "turned_guide": "Tour guide {status}",
+            "turned_measure": "Ruler {status}",
+            "shortcut_copy": "One-click copy",
+            "share_link": "Share links to friends",
+            "measure_show_tips": "Please use the measuring function on the display page",
+            "delete_measure_line": "Delete measurement line",
+            "please_click_tips": "'Please click“allow”'",
+            "vr_fail_app_tips": "The browser failed to detect the rotation. Please enable the settings such as motion and direction access in the phone or browser settings, and then refresh this page.",
+            "vr_fail_safari_tips": "The browser failed to detect the rotation. To fully experience the VR effect, please open the \"Motion and Direction Access\" switch under \"Settings\"> \"Safari\"> \"Privacy and Security\", and then refresh this page.",
+            "loading_bottom_text": "4Dage provides technical support",
+            "vr_fail_reopen_tips": "Can't access motion and orientation, please restart App and try again.",
+            "add_music_title": "Add BGM",
+            "add_music_tips": "Support MP3, WAV and other audio formats, no more than 5MB",
+            "re_add_music": "Re-add",
+            "re_add_title": "Re-add BGM",
+            "re_add_tips": "The latest music will replace the added music, <br>Are you sure to continue adding?",
+            "re_add_mobile_tips": "Re-adding will replace the added music",
+            "delete_tips": "Are you sure to delete the current BGM?",
+            "delete_title": "Delete BGM",
+            "bgm_empty_tips": "Please select BGM",
+            "wechat": "Wechat",
+            "friend_circle": "Moments"
+        },
+        "information": {
+            "__name": "场景信息",
+            "title": "Title",
+            "title_tips": "Please enter a title",
+            "title_require": "Please add a title (max {limit} characters).",
+            "description": "Description",
+            "description_tips": "Please enter a description.",
+            "link": "Add a link",
+            "link_text_tips": "Link title",
+            "link_href_tips": "Link",
+            "link_text_require": "Please fill in the text.",
+            "link_href_require": "Please fill in the link.",
+            "classify": "Choose a category",
+            "upload_time": "Upload date",
+            "record": "Not recorded",
+            "shoot_count": "Number of shots",
+            "password": "Set a password",
+            "password_tips": "4 characters",
+            "password_desc": "Set a password if you don't want it to be accessible for the public.",
+            "password_require": "Please enter a password with {limit} digits",
+            "logo_edit": "Edit the logo",
+            "logo_exit": "Finish editting logo",
+            "logo_show_bottom": "Show original logo",
+            "logo_style1": "Logo style 1",
+            "logo_style2": "Logo style 2",
+            "logo_delete": "Delete the uploaded logo?"
+        },
+        "screen": {
+            "__name": "初始画面",
+            "current": "Current cover",
+            "current_set": "Set as scene cover",
+            "tips": "Drag the screen, click and save your scene cover."
+        },
+        "hotspot": {
+            "__name": "添加热点",
+            "add": "Add hotspot",
+            "edit": "Edit hotspot",
+            "count": "Added hotspots",
+            "location": "Hotspot Location",
+            "location_tips": "Drag the hotspot to another position.",
+            "location_modify": "Modify hotspot location",
+            "location_confirm": "Confirm hotspot location",
+            "location_desc": "Please drag the hot spot in the two scene areas on the left and align it to the desired point.",
+            "style": "Choose hotspot style",
+            "style_desc": "Choose the default style, or manually upload the picture to customize the style, upload the picture in PNG/JPG format",
+            "style_name": "Style",
+            "style_dele": "Sure to delete this style?",
+            "style_manage": "Manage",
+            "style_exit": "Quit",
+            "media_photo": "Add pictures to enrich hot content",
+            "media_video": "Upload local videos for more display",
+            "media_voice": "Upload local audio content for hotspot explanation",
+            "media_link": "You can add a hyperlink to the video, and the video will be played in the hotspot",
+            "title": "Title",
+            "title_tips": "Please enter a title",
+            "title_require": "Please add a title (max {limit} characters).",
+            "description": "Description",
+            "description_tips": "Please enter a description.",
+            "text_link": "Add a link",
+            "text_link_text_tips": "Please fill in the link text",
+            "text_link_href_tips": "Please fill in the link address",
+            "text_link_text_require": "Link text cannot be empty",
+            "text_link_href_require": "Link address cannot be empty",
+            "link": "Add a link",
+            "link_require": "Please add an external link",
+            "photo": "Photo",
+            "voice": "Audio",
+            "video": "Video",
+            "photo_tips": "Support JPG, PNG and other image formats",
+            "voice_tips": "Support MP3, WAV and other audio formats",
+            "video_tips": "Support MP4 format. <20M",
+            "photo_require": "Please add photos",
+            "voice_require": "Please add audio",
+            "video_require": "Please add videos",
+            "m_location_tips1": "Point split-screen hotspots above and below to the marked target",
+            "m_location_tips2": "Determine the hotspot location, click next to edit content",
+            "m_location_up": "Above",
+            "m_location_left": "Left side",
+            "m_location_move_tips": "The auxiliary calibration position may be inaccurate,<br>please check and drag to {direction} the same location",
+            "set_visible_btn": "Hotspot Visiblity",
+            "save_visible_btn": "Save",
+            "save_hotspot_done": "Successfully saved hotspot",
+            "save_hotspot_fail": "Failed to save hotspot",
+            "delete_hotspot_tips": "Do you want to delete the current hotspot?",
+            "delete_hotspot_done": "Successfully deleted hotspot",
+            "delete_hotspot_fail": "Failed to delete hotspot",
+            "cant_add_hotspot_tips": "Unable to add hotspot",
+            "cant_add_hotspot_content": "The number of hotspots has reached the maximum: {limit}",
+            "link_text_tips": "Link title",
+            "link_href_tips": "Link",
+            "link_text_require": "Please fill in the text",
+            "link_href_require": "Please fill in the link",
+            "add_media": "Add Content"
+        },
+        "guide": {
+            "__name": "自动导览",
+            "route": "Tour Route",
+            "view": "Switch View",
+            "record": "Start recording",
+            "record_audio": "Record",
+            "pause": "Pause",
+            "stop": "Stop",
+            "end": "End",
+            "delete": "Delete",
+            "continue": "Continue recording",
+            "preview": "Preview",
+            "clear": "Clear",
+            "sync": "Audiovisual synchronization",
+            "sound": "Record audio",
+            "file": "Upload audio",
+            "file_add": "Add audio",
+            "tips": "Click \"Start\" to record the tour.",
+            "start": "Start",
+            "finish": "Finish",
+            "less": "Less",
+            "replace_tips": "Do you want to start over the tour recording?",
+            "replace_content": "Redo the recording will overwrite previous data.",
+            "sound_open_fail_tips": "Failed to turn on the microphone",
+            "sound_tips": "Failed to turn on the microphone, continue recording?",
+            "sound_content": "Please allow this site to use the microphone in your browser settings and refresh the page.",
+            "upload_sound_done": "Upload sound explanation successfully",
+            "upload_sound_fail": "Failed to upload sound explanation",
+            "delete_sound_done": "Delete sound explanation successfully",
+            "delete_sound_fail": "Failed to delete sound explanation",
+            "room_title": "Title",
+            "room_title_tips": "Please enter the room title",
+            "room_title_require": "Please enter a title",
+            "room_panel_title": "Guide information",
+            "room_sound_title": "Record audio",
+            "delete_video_content": "Your current recording will be deleted",
+            "delete_file_content": "Your currently uploaded sound explanation will be deleted",
+            "delete_sound_content": "Your recorded audio will be deleted",
+            "camera_save_success": "Successfully saved camera",
+            "clear_video_tip": "Your current recording will be cleared",
+            "saving_sound": "Saving recording",
+            "save_sound_done": "Successfully saved recording",
+            "save_sound_fail": "Failed to save recording",
+            "no_sound_tips": "Currently does not support recordig.<br>Please record on WeChat or computer",
+            "sound_success_tips": "Successfully opened the microphone",
+            "sound_fail_tips": "Currently does not support recording.<br>Official account information configuration error",
+            "wechat_sound_fail_tips": "Failed to open WeChat microphone",
+            "open_sound_guide_tips": "If you want to record, please enable it in the WeChat settings-privacy-authorization management. And ensure that the recording equipment is normal",
+            "sound_cant_open_tips": "This browser does not support recording. <br>It is recommended to replace other mainstream browsers for a better experience",
+            "replace_sound_tips": "Re-record?",
+            "replace_sound_content": "Current sound explanation will be replaced",
+            "merge_sound_fail": "Fail to merge sound explanation",
+            "sound_limit": "The recording duration cannot exceed {time} minutes",
+            "select_to_record": "Please select the corresponding point to record the guide in this area"
+        },
+        "sign": {
+            "title": "Logo style",
+            "size": "Size",
+            "style0": "Style one",
+            "style1": "Style two",
+            "style2": "Style three",
+            "style3": "Upload"
+        },
+        "walk": {
+            "title": "Roaming Possibility",
+            "tips1": "Further optimize the experience during roaming by setting roaming possibility, such as cases in which one may penetrate through the walls. ",
+            "tips2": "Set the roaming possibility by clicking and setting the connection of each roaming point.",
+            "save": "Save current settings",
+            "hide": "Hide the spot",
+            "show": "Show the spot",
+            "pano_tips": "Tip: You hide the initial point, so you can't roam in this scene."
+        },
+        "model": {
+            "__name": "修整模型",
+            "cad": "CAD View",
+            "cad_download": "FloorPlan Download",
+            "view": "Switch View",
+            "title": "Add structure",
+            "title_door": "Doors and windows",
+            "title_component": "Components",
+            "title_other": "Others",
+            "attribute": "Attributes",
+            "door": "Door",
+            "slideDoor": "Slidedoor",
+            "casement": "Window",
+            "bayCase": "Bay Window",
+            "groundCase": "French Sash",
+            "column": "Column",
+            "furnColumn": "Frame Column",
+            "furnFlue": "Flue",
+            "point": "Point",
+            "line": "Interior Wall",
+            "tagging": "Tag",
+            "tagging_name_tips": "Tag name",
+            "tagging_area_tips": "Enter area",
+            "direction": "Compass",
+            "wallLine": "Interior wall",
+            "widget_delete": "{widget} will be deleted",
+            "panel_btn_default": "Reset",
+            "panel_btn_delete": "Delete",
+            "attr_angle": "Rotation angle",
+            "attr_within": "Flip direction",
+            "attr_ewidth": "Width",
+            "attr_eheight": "Height",
+            "attr_tick": "Tickness",
+            "attr_showTitle": "Tag name",
+            "attr_showContent": "Tag area",
+            "attr_top": "Top",
+            "attr_bottom": "Bottom",
+            "success": "Add successfully {widget}",
+            "error_location": "Current location cannot be added {widget}",
+            "error_outdoor": "Outdoor cannot be added {widget}",
+            "error_something": "Building at the current location cannot be added {widget}",
+            "error_widget": "Current location cannot be added {widget}",
+            "enter_adjust_floor": "Ground adjustment",
+            "exit_adjust_floor": "Exit adjustment mode",
+            "color_title": "Protractor Color:"
+        },
+        "videos": {
+            "__name": "添加视频",
+            "tips": "Please click on the reference plane to set the video position.",
+            "panel_title": "Video attributes",
+            "panel_preview": "Preview",
+            "panel_upload": "Upload",
+            "panel_upload_tips": "Support MP4 format",
+            "panel_move": "Move",
+            "panel_zoom": "Zoom",
+            "panel_thickness": "Tickness",
+            "recoverRatio": "Restore original aspect ratio",
+            "recoverRatioTip": "Restore original aspect ratio of the video"
+        },
+        "custom": {
+            "model_title": "Download model",
+            "uploading": "Uploading…",
+            "download": "Original model download",
+            "download_panos": "Download panos",
+            "upload": "Modified model upload",
+            "title1": "Model download/upload tutorial",
+            "tips11": "1.After downloading the ZIP package of the scene model, open the decompressed file”mesh.obj” to edit it.",
+            "tips12": "2.After the edit is completed, it is recommended to render the model texture. The render texture should be controlled within 1.5M while the saved obj files should be controlled within 3M.",
+            "tips13": "3.After completing steps 1 and 2, package the obj, mtl, and texture into a zip package and upload it.",
+            "title2": "Notes:",
+            "tips21": "1.Please control the file size, in order not to freeze when you are browsing which will affect your experience.",
+            "tips22": "2.Please use the same name in both obj and ZIP package, otherwise the replacement will fail.",
+            "get_image_fail": "Failed to get image. Please check your network settings and try again.",
+            "download_model_fail": "Fail to download model",
+            "reupload_tips": "Upload {type}",
+            "panoramic_upload": "Pano Upload",
+            "panoramic_upload_tips": "Panorama upload",
+            "panoramic_upload_box_tips": "Please upload JPG image<br>with corresponding point name.",
+            "panoramic_download": "Pano Download",
+            "panoramic_download_tips": "Panorama download",
+            "panoramic": "Panorama",
+            "ball_video": "Video",
+            "ball_video_upload_tips": "Video Upload",
+            "ball_video_download_tips": "Video Download",
+            "ball_video_upload_box_tips": "The panoramic video supports MP4 format<br>which should not exceed 1024M.",
+            "upload_title": "Upload files",
+            "upload_format_error": "File format error, please upload again",
+            "upload_name_error": "File name error, please upload again",
+            "upload_success": "The upload is successful and will take effect<br>after saving and publishing",
+            "upload_code_5017": "Failed to upload the model, <br>please refer tothe tutorial on the right",
+            "upload_code_5018": "The zip file can only have<br>one level of directory or no directory",
+            "upload_code_5019": "There must be only one obj and mtl file",
+            "upload_code_5020": "The texture needs to be controlled within 1.5M, and the obj file needs to be controlled within 3M",
+            "upload_code_5012": "The data is abnormal",
+            "upload_code_5023": "The upload file format is incorrect, <br>only jpg or mp4 format.",
+            "download_tips": "Download {type}",
+            "download_fail": "Failed to download"
+        },
+        "vrhouse": {
+            "__name": "VR看房",
+            "linkto_management": "Go to the VR house management background"
+        },
+        "business": {
+            "__name": "商圈模块",
+            "list_title": "Scene hotspot list",
+            "linkto_management": "Go to the shop management background"
+        },
+        "link": {
+            "__name": "跳转关联",
+            "title1": "Save the link",
+            "title2": "Edit the link",
+            "title3": "Panorama association",
+            "list_text": "Added links",
+            "btn_new_text": "Add a link",
+            "btn_add_title": "Positioning the connection point",
+            "btn_add_text": "Determine the connection point position",
+            "btn_add_desc": "Mark and drag the connection point to the suitable position",
+            "btn_edit_text": "Adjust the link position",
+            "style": "Link icon",
+            "style_name": "icon",
+            "style_desc": "upport customized icon with recommended size of 128*128 pixels. Support JPG/PNG format.",
+            "style_dele": "Delete this icon? <br /> The same icon used for other links will also be deleted.",
+            "desc_title": "Description",
+            "desc_tips": "Please enter a description.",
+            "desc_require": "Please enter a description.",
+            "type": "Choose the type of link",
+            "type_photo": "upport uploading panoramic pictures with an aspect ratio of 2:1, recommended pixel size should not be less than 6000 × 3000 pixels, and the file should not exceed 120M",
+            "type_photo_require": "Upload panoramic pictures.",
+            "type_link": "Please input the scene link.",
+            "type_link_tips": "http(s)://",
+            "type_link_require": "Please input the scene link.",
+            "enter_title": "Link cover",
+            "enter_require": "Please set a link cover",
+            "enter_btn_text": "Set a link cover",
+            "outer_title": "Exit spot position",
+            "outer_style": "Exit spot icon",
+            "outer_desc": "Support customized icon with recommended size of 128*128 pixels. Support JPG/PNG format.",
+            "outer_require": "Please set the position of exit spot.",
+            "outer_btn_text": "Set the position of exit spot.",
+            "links_title": "Select the panorama",
+            "links_tips": "Select the panorama and drag it to the corresponding position on the left to set the jump.",
+            "links_cancel_tips": "Cancel association settings"
+        }
+    },
+    "login": {
+        "__name": "登录",
+        "title": "Message",
+        "login": "Log in",
+        "logon": "Log in and continue",
+        "login_tips": "You are not logged in. Please log in to edit."
+    },
+    "common": {
+        "__name": "公用",
+        "on": "on",
+        "off": "off",
+        "add": "Add",
+        "set": "OK",
+        "ok": "OK",
+        "save": "Save",
+        "cancel": "Cancel",
+        "complete": "Complete",
+        "edit": "Modify",
+        "giveup": "Give up",
+        "commit": "Commit",
+        "photo": "Photo",
+        "voice": "Audio",
+        "video": "Video",
+        "bgm": "BGM",
+        "crop": "Crop",
+        "upload": "Upload",
+        "download": "Download",
+        "change": "Change",
+        "unnamed": "Unnamed",
+        "publish": "Save and publish",
+        "publish_text": "Save and publish successfully!",
+        "publish_tips": "Check your scene immediately?",
+        "publish_btn_ok": "Now",
+        "publish_btn_no": "Later",
+        "public": "Public",
+        "private": "Private",
+        "waiting": "Waiting...",
+        "audio": "Audio",
+        "second": "s",
+        "delete": "Delete",
+        "prev": "Go back",
+        "next": "Next",
+        "meter": "≈{meter}m",
+        "guide": "Tour Guide",
+        "rule": "Ruler",
+        "roaming": "Roaming",
+        "continue": "Continue",
+        "ensure_delete": "Delete",
+        "text_limit": "Limit within {limit} characters",
+        "default": "Default",
+        "custom": "Customize",
+        "back": "Back",
+        "will_delete": "Will be deleted",
+        "setup": "Set",
+        "exit": "Done",
+        "music": {
+            "__name": "背景音乐",
+            "none": "No music",
+            "cheerfu": "Cheerful",
+            "ethereal": "Ethereal",
+            "rhythmic": "Rhythmic",
+            "nostalgic": "Nostalgic",
+            "missing": "Missing",
+            "retro": "Retro",
+            "strings": "Strings",
+            "happy": "Happy"
+        },
+        "category": {
+            "__name": "分类",
+            "museum": "Museum",
+            "estate": "Real estate",
+            "eshop": "E-Commerce",
+            "catering": "Catering",
+            "home": "Home",
+            "other": "Other"
+        },
+        "uploads": {
+            "__name": "文件上传",
+            "uploading": "Uploading",
+            "uploaded": "Uploaded",
+            "wait": "Wait...",
+            "error": "Error",
+            "add": "Add",
+            "start": "Start",
+            "file_require": "Please add file",
+            "cant_upload": "Your browser does not support uploading files",
+            "not_support": "The file you selected is not {fileType} supported by the browser, please select again",
+            "too_large": "The file is too large and cannot be larger than {size} trillion",
+            "too_large_reupload_tips": "The uploaded video is too large, please upload again"
+        },
+        "tips": {
+            "__name": "提示",
+            "wait": "Please wait..."
+        }
+    },
+    "tips": {
+        "__name": "提示",
+        "title": "Tip",
+        "set_done": "Set up successfully",
+        "set_fail": "Set up failed",
+        "save_done": "Save successfully",
+        "save_fail": "Save failed",
+        "delete": "Delete or not?",
+        "delete_done": "Delete successfully",
+        "delete_fail": "Fail to delete",
+        "upload_done": "Upload successfully",
+        "upload_fail": "Failed to upload",
+        "exception": "Error",
+        "network_error": "The internet is disconnected, please try it again",
+        "file_notfound": "The file is not found",
+        "scene_notfound": "The scene is not found",
+        "params_notfound": "The parameter is not found",
+        "camera_notfound": "The matched camera is not found",
+        "password_error": "Incorrect password",
+        "data_error": "The data is not found",
+        "auth_deny": "Unathorized to operate the scene",
+        "clear": "Sure to clear?",
+        "upload_pic_fail": "Failed to upload pictures",
+        "wait": "Please waiting...",
+        "house_type_save_fail": "Failed to save house type"
+    },
+    "show": {
+        "__name": "展示页面",
+        "on": "On",
+        "off": "Off",
+        "measure": "Measuring tool",
+        "measure_start": "Start",
+        "measure_end": "End",
+        "measure_button": "Confirm {status}",
+        "location_up": "Above",
+        "location_left": "Left",
+        "location_start_tips": "Please click on {direction}",
+        "location_end_tips": "The starting point is determined, please locate the end point",
+        "vr": "VR mode",
+        "share": "Share it",
+        "music": "Turn {status} music",
+        "password_tips": "Password",
+        "password_require": "Please enter the password"
+    },
+    "components": {
+        "user_guid": {
+            "__name": "用户引导",
+            "title": "Operation Tips",
+            "pano": "Walk<br />Click to move.",
+            "rotate": "Roam<br />Swipe the screen to roam.",
+            "zoom": "Zoom<br />Zoom in or out.",
+            "set": "Got it"
+        }
+    }
+}

+ 482 - 0
lang/_fr.js

@@ -0,0 +1,482 @@
+module.exports = {
+    "menu": {
+        "__name": "Menu",
+        "music": "musique",
+        "base": "Paramètres",
+        "information": "Scène Info",
+        "screen": "Couverture de la Scène",
+        "hotspot": "Ajouter Hotspot",
+        "guide": "Visite Guidée",
+        "sign": "Spot Logo",
+        "walk": "Possibilité de roaming",
+        "model": "Modification du modèle",
+        "custom": "Upload Download",
+        "videos": "Ajouter Vidéos",
+        "vrhouse": "VR maison",
+        "business": "Section Business",
+        "scene": "Transition Scène",
+        "video": "Ajouter vidéo",
+        "decor": "Décore"
+    },
+    "modules": {
+        "__name": "Module",
+        "base": {
+            "__name": "Paramètres",
+            "qrcode": "QR Code",
+            "qrcode_download": "Télécharger le code QR",
+            "qrcode_tips": "Personnaliser votre logo",
+            "scene_link": "Link de la Scène",
+            "scene_link_copy": "Copier le link",
+            "scene_link_copy_tips": "Copié",
+            "bgm": "Musique",
+            "pano_text": "Roaming",
+            "mode_2d_text": "Plan",
+            "mode_3d_text": "Modèle 3D",
+            "map_text": "Mini-carte",
+            "vr_text": "Mode VR",
+            "vr_tips": "Regardez l'effet VR sur la page d'affichage mobile",
+            "guide_text": "Visite Guidée",
+            "rule_text": "Mesures",
+            "cad_text": "Plan",
+            "measure_text": "Règle",
+            "measure_tips": "La fonction de mesure ne peut pas être utilisée en mode d'éditing, veuillez l'utiliser sur la page d'affichage",
+            "turned_vr": "VR allumé {status}",
+            "turned_map": "Fonction Mini-carte {status}",
+            "turned_cad": "Plan {status}",
+            "turned_m2d": "Vue en plan {status}",
+            "turned_m3d": "Vue 3D {status}",
+            "turned_pano": "Vue Roaming {status}",
+            "turned_rule": "Fonction de measure {status}",
+            "turned_guide": "Visite guidé {status}",
+            "turned_measure": "Règle {status}",
+            "shortcut_copy": "Copier en un click",
+            "share_link": "Partager le link avec vos amis",
+            "measure_show_tips": "Veuillez utiliser la fonction de mesure sur la page d'affichage",
+            "delete_measure_line": "Effacer les liges de mesure",
+            "please_click_tips": "'Veuillez clicker sure “autoriser”'",
+            "vr_fail_app_tips": "Le navigateur n'a pas réussi à détecter la rotation. Veuillez activer les paramètres tels que l'accès au mouvement et à la direction dans les paramètres du téléphone ou du navigateur, puis actualisez cette page.",
+            "vr_fail_safari_tips": "Le navigateur n'a pas réussi à détecter la rotation. Pour profiter pleinement de l'effet VR, veuillez ouvrir le commutateur \"Accès aux mouvements et à la direction \" sous \"Paramètres \"> \"Safari \"> \"Confidentialité et sécurité \", puis actualisez cette page.",
+            "loading_bottom_text": "4Dage offre le support technique",
+            "vr_fail_reopen_tips": "Impossible d'accéder au mouvement et à l'orientation, veuillez redémarrer l'application et réessayer.",
+            "add_music_title": "Ajouter Musique",
+            "add_music_tips": "Nous acceptons les formats MP3, WAV et d'autres formats audio, avec un max de 5MB",
+            "re_add_music": "Re-ajouter",
+            "re_add_title": "Re-ajouter Musique",
+            "re_add_tips": "La dernière musique remplacera la musique déjà ajoutée, <br> Êtes-vous sûr de continuer à ajouter?",
+            "re_add_mobile_tips": "Re-ajouter remplacera la musique déjà ajoutée",
+            "delete_tips": "Êtes-vous sûr d'effacer la musique actuelle?",
+            "delete_title": "Effacer Musique",
+            "bgm_empty_tips": "Veuillez sélectionner la Musique",
+            "wechat": "Wechat",
+            "friend_circle": "Moments"
+        },
+        "information": {
+            "__name": "Informations sur la scène",
+            "title": "Titre",
+            "title_tips": "Veuillez entrer un titre",
+            "title_require": "Veuillez ajouter un titre (max {limit} caractères).",
+            "description": "Description",
+            "description_tips": "Veuillez entrer une description.",
+            "link": "Ajouter un link",
+            "link_text_tips": "Titre du link",
+            "link_href_tips": "Link",
+            "link_text_require": "Veuillez compléter le texte.",
+            "link_href_require": "Veuillez compléter le link.",
+            "classify": "Choisir une catégorie",
+            "upload_time": "Date du chargement",
+            "record": "Non enregistré",
+            "shoot_count": "Nombre de positions",
+            "password": "Définissez un mot de passe",
+            "password_tips": "4 caractères",
+            "password_desc": "Définissez un mot de passe si vous ne voulez pas que le modèle soit accessible au publique.",
+            "password_require": "Veuillez entrer un mot de passe ave {limit} chiffres",
+            "logo_edit": "Editer le logo",
+            "logo_exit": "Finir d'éditer le logo",
+            "logo_show_bottom": "Montrer le logo original",
+            "logo_style1": "Logo style 1",
+            "logo_style2": "Logo style 2",
+            "logo_delete": "Effacer le logo chargé?"
+        },
+        "screen": {
+            "__name": "Écran initial",
+            "current": "Couverture actuelle",
+            "current_set": "Sélectionner couverture",
+            "tips": "Bouger l'écran, clicker et sauver votre couverture de scène."
+        },
+        "hotspot": {
+            "__name": "Ajouter un hotspot",
+            "add": "Ajouter un hotspot",
+            "edit": "Editer hotspot",
+            "count": "Hotspots ajoutés",
+            "location": "Emplacement du Hotspot",
+            "location_tips": "Tirer le hotspot dans une autre position.",
+            "location_modify": "Modifier la position du hotspot",
+            "location_confirm": "Confirmer la position du hotspot",
+            "location_desc": "Faites glisser le hotspot dans les deux zones de scène sur la gauche et alignez-le sur le point souhaité.",
+            "style": "Choisir style du hotspot",
+            "style_desc": "Choisissez le style de base, ou charger manuellement une image pour personnaliser le style, charger une image au format PNG/JPG",
+            "style_name": "Style",
+            "style_dele": "Êtes-vous sûr d'effacer ce style?",
+            "style_manage": "Gérer",
+            "style_exit": "Fermer",
+            "media_photo": "Ajoutez images pour enrichir le contenu du hotspot",
+            "media_video": "Charger des videos locales pour plus d'affichage",
+            "media_voice": "Charger du contenu audio local pour expliquer le hotspot",
+            "media_link": "Vous pouvez ajouter un lien hypertexte vers la vidéo et la vidéo sera lue dans le hotspot",
+            "title": "Titre",
+            "title_tips": "Veuillez entrer un titre",
+            "title_require": "Veuillez entrer un titre (max {limit} caractères).",
+            "description": "Description",
+            "description_tips": "Veuillez entrer une description.",
+            "text_link": "Ajouter un link",
+            "text_link_text_tips": "Veuillez compléter le texte du link",
+            "text_link_href_tips": "Veuillez compléter l'addresse du link",
+            "text_link_text_require": "Le texte du link ne peut pas être vide",
+            "text_link_href_require": "L'addresse du link ne peut pas être vide",
+            "link": "Ajouter un link",
+            "link_require": "Veuillez ajouter un link externe",
+            "photo": "Photo",
+            "voice": "Audio",
+            "video": "Vidéo",
+            "photo_tips": "Supporte les formats JPG, PNG et autres formats image",
+            "voice_tips": "Supporte MP3, WAV et autres formats audio",
+            "video_tips": "Supporte format MP4. <20M",
+            "photo_require": "Veuillez ajouter des photos",
+            "voice_require": "Veuillez ajouter un audio",
+            "video_require": "Veuillez ajouter des vidéos",
+            "m_location_tips1": "Pointez les hotspots de l'écran partagé au-dessus et en dessous de la cible marquée",
+            "m_location_tips2": "Déterminez la position du hotspot, clicker sur next pour éditer contenu",
+            "m_location_up": "Au-dessus",
+            "m_location_left": "Côté gauche",
+            "m_location_move_tips": "La position d'étalonnage auxiliaire peut être inexacte , <br> veuillez vérifier et faire glisser vers {direction} le même emplacement",
+            "set_visible_btn": "Visibilité des hotspots",
+            "save_visible_btn": "Sauver",
+            "save_hotspot_done": "Hotspot enregistré avec succès",
+            "save_hotspot_fail": "Échec de l'enregistrement du hotspot",
+            "delete_hotspot_tips": "Voulez-vous effacer ce hotspot?",
+            "delete_hotspot_done": "Hotspot effacé avec succès",
+            "delete_hotspot_fail": "Échec de la suppression du hotspot",
+            "cant_add_hotspot_tips": "Impossible d'ajouter le hotspot",
+            "cant_add_hotspot_content": "Le nombre de hotspots a atteint le maximum: {limit}",
+            "link_text_tips": "Titre du link",
+            "link_href_tips": "Link",
+            "link_text_require": "Veuillez compléter le texte",
+            "link_href_require": "Veuillez compléter le link",
+            "add_media": "Ajouter du contenu"
+        },
+        "guide": {
+            "__name": "Visite Guidée",
+            "route": "Trajet du tour",
+            "view": "Changer la Vue",
+            "record": "Commencer l'enregistrement",
+            "record_audio": "Enregistrer",
+            "pause": "Pause",
+            "stop": "Stop",
+            "end": "Fin",
+            "delete": "Effacer",
+            "continue": "Continuer l'enregistrement",
+            "preview": "Aperçu",
+            "clear": "Nettoyer",
+            "sync": "Synchronisation Audiovisuel",
+            "sound": "Enregistrer audio",
+            "file": "Charger audio",
+            "file_add": "Ajouter audio",
+            "tips": "Clickez \"Commencer\" pour enregistrer la visite.",
+            "start": "Commencer",
+            "finish": "Finir",
+            "less": "<",
+            "replace_tips": "Voulez-vous recommencer l'enregistrement de la visite guidée?",
+            "replace_content": "Refaire l'enregistrement effacera les données précédentes.",
+            "sound_open_fail_tips": "Échec de l'activation du microphone",
+            "sound_tips": "Échec de l'activation du microphone, continuer l'enregistrement?",
+            "sound_content": "Veuillez autoriser ce site à utiliser le microphone dans les paramètres de votre navigateur et actualiser la page.",
+            "upload_sound_done": "Charger l'explication sonore avec succès",
+            "upload_sound_fail": "Échec du chargement de l'explication sonore",
+            "delete_sound_done": "Suppression de l'explication sonore avec succès",
+            "delete_sound_fail": "Échec de la suppression de l'explication sonore",
+            "room_title": "Titre",
+            "room_title_tips": "Veuillez entrer le titre de la chambre",
+            "room_title_require": "Veuillez entrer un titre",
+            "room_panel_title": "Information du guide",
+            "room_sound_title": "Enregistrer audio",
+            "delete_video_content": "Votre enregistrement actuel sera supprimé",
+            "delete_file_content": "Votre explication sonore actuellement chargée sera supprimée",
+            "delete_sound_content": "Votre audio enregistré sera supprimé",
+            "camera_save_success": "Caméra enregistrée avec succès",
+            "clear_video_tip": "Votre enregistrement actuel sera effacé",
+            "saving_sound": "Sauver l'enrgistrement sonore",
+            "save_sound_done": "Bande sonore enregistré avec succès",
+            "save_sound_fail": "Échec de l'enregistrement de la bande sonore",
+            "no_sound_tips": "Actuellement, nous ne supportons pas l'enregistrement. <br> Veuillez enregistrer sur WeChat ou sur un ordinateur",
+            "sound_success_tips": "Ouverture réussie du microphone",
+            "sound_fail_tips": "Actuellement, ne prend pas en charge l'enregistrement. <br> Erreur de configuration des informations de compte officiel",
+            "wechat_sound_fail_tips": "Échec de l'ouverture du microphone WeChat",
+            "open_sound_guide_tips": "Si vous souhaitez enregistrer, veuillez l'activer dans la gestion des paramètres WeChat-confidentialité-autorisation. Et assurez-vous que l'équipement d'enregistrement est normal",
+            "sound_cant_open_tips": "Ce navigateur ne prend pas en charge l'enregistrement. <br> Il est recommandé de d'utiliser d'autres navigateurs grand public pour une meilleure expérience",
+            "replace_sound_tips": "Re-enregistrer?",
+            "replace_sound_content": "L'explication sonore actuelle sera remplacée",
+            "merge_sound_fail": "Échec de la fusion des explication sonores",
+            "sound_limit": "La durée d'enregistrement ne peut pas dépasser {time} minutes",
+            "select_to_record": "Veuillez selectionner le point correspondand pour enregistrer la visite guidée dans cette zone"
+        },
+        "sign": {
+            "title": "Style du Logo",
+            "size": "Taille",
+            "style0": "Style un",
+            "style1": "Style deux",
+            "style2": "Style trois",
+            "style3": "Charger"
+        },
+        "walk": {
+            "title": "Possibilité de Roaming",
+            "tips1": "Optimisez davantage l'expérience de la visite en définissant la possibilité de roaming, par example dans les cas dans lesquels on peut pénétrer à travers les murs.",
+            "tips2": "Définissez la possibilité de roaming en cliquant et en définissant la connexion de chaque point.",
+            "save": "Sauver les paramètres actuels",
+            "hide": "Cacher le point",
+            "show": "Montrer le point"
+        },
+        "model": {
+            "__name": "Modifier le modèle",
+            "cad": "Vue CAD",
+            "cad_download": "Télecharger le Plan",
+            "view": "Changer de Vue",
+            "title": "Ajouter une structure",
+            "title_door": "Portes et fenêtres",
+            "title_component": "Composants",
+            "title_other": "Autres",
+            "attribute": "Attributs",
+            "door": "Porte",
+            "slideDoor": "Porte coulissante",
+            "casement": "Fenêtre",
+            "bayCase": "Fenêtre en saillie",
+            "groundCase": "Fenêtre à la française",
+            "column": "Colonne",
+            "furnColumn": "Colonne de cadre",
+            "furnFlue": "Cheminée",
+            "point": "Point",
+            "line": "Mur intérieur",
+            "tagging": "Tag",
+            "tagging_name_tips": "Nom du Tag",
+            "tagging_area_tips": "Entrer zone",
+            "direction": "Boussole",
+            "wallLine": "Mur intérieur",
+            "widget_delete": "{widget} va être effacé",
+            "panel_btn_default": "Réinitialiser",
+            "panel_btn_delete": "Effacer",
+            "attr_angle": "Angle de rotation",
+            "attr_within": "Inverser la direction",
+            "attr_ewidth": "Largeur",
+            "attr_eheight": "Hauteur",
+            "attr_tick": "Épaisseur",
+            "attr_showTitle": "Nom du tag",
+            "attr_showContent": "Surface de la zone",
+            "attr_top": "Haut",
+            "attr_bottom": "Bas",
+            "success": "{widget} ajouter avec succès",
+            "error_location": "La position actuelle ne peut pas être ajoutée {widget}",
+            "error_outdoor": "Outdoor cannot be added {widget}",
+            "error_something": "Structure à la position actuelle ne peut pas être ajoutée {widget}",
+            "error_widget": "La position actuelle ne peut pas être ajoutée {widget}",
+            "enter_adjust_floor": "Réglage du sol",
+            "exit_adjust_floor": "Sortir du mode Réglage",
+            "color_title": "Couleur du rapporteur:"
+        },
+        "videos": {
+            "__name": "Ajouter une vidéo",
+            "tips": "Veuillez cliquer sur le plan de référence pour définir la position de la vidéo.",
+            "panel_title": "Attributs de la vidéo",
+            "panel_preview": "Aperçu",
+            "panel_upload": "Upload",
+            "panel_upload_tips": "Supporte le format MP4",
+            "panel_move": "Bouger",
+            "panel_zoom": "Zoomer",
+            "panel_thickness": "Épaisseur",
+            "recoverRatio": "Restaurer le ratio d'origine",
+            "recoverRatioTip": "Restaurer le ratio d'origine de la vidéo"
+        },
+        "custom": {
+            "model_title": "Télécharger le modèle",
+            "uploading": "Chargement…",
+            "download": "Télecharger Origina",
+            "download_panos": "Télécharger panos",
+            "upload": "Charger le modèle modifié",
+            "title1": "Tutoriel pour le téléchargement/chargement du modèle",
+            "tips11": "1.Après avoir téléchargé le package ZIP du modèle de scène, ouvrez le fichier décompressé «mesh.obj» pour le modifier.",
+            "tips12": "2.Une fois la modification terminée, il est recommandé de rendre la texture du modèle. La texture de rendu doit être contrôlée à moins de 1,5 M tandis que les fichiers obj enregistrés doivent être contrôlés dans 3M.",
+            "tips13": "3.Après avoir terminé les étapes 1 et 2, empaquetez l'objet, le mtl et la texture dans un paquet zip et chargez-le.",
+            "title2": "Notes:",
+            "tips21": "1.Veuillez contrôler la taille du fichier, afin de ne pas geler lorsque vous naviguez, ce qui affectera votre expérience.",
+            "tips22": "2.Veuillez utiliser le même nom dans les packages obj et ZIP, sinon le remplacement échouera.",
+            "get_image_fail": "Impossible d'obtenir l'image. Veuillez vérifier vos paramètres réseau et réessayer.",
+            "download_model_fail": "Échec du téléchargement du modèle",
+            "reupload_tips": "Charger {type}",
+            "panoramic_upload": "Charger Pano",
+            "panoramic_upload_tips": "Charger Panorama",
+            "panoramic_upload_box_tips": "Veuillez charger l'image JPG <br> avec le nom de point correspondant.",
+            "panoramic_download": "Télécharger Pano",
+            "panoramic_download_tips": "Télécharger Panorama",
+            "panoramic": "Panorama",
+            "ball_video": "Vidéo",
+            "ball_video_upload_tips": "Charger Vidéo",
+            "ball_video_download_tips": "Télécharger Vidéo",
+            "ball_video_upload_box_tips": "La vidéo panoramique prend en charge le format MP4 <br> qui ne doit pas dépasser 1024M.",
+            "upload_title": "Charger les fichiers",
+            "upload_format_error": "Erreur de format de fichier, veuillez réimporter",
+            "upload_name_error": "Erreur du nom de fichier, veuillez réimporter",
+            "upload_success": "L'importation est réussie et prendra effet <br> après l'enregistrement et la publication",
+            "upload_code_5017": "Échec du chargement du modèle, <br> veuillez vous reporter au didacticiel à droite",
+            "upload_code_5018": "Le fichier zip ne peut avoir qu'un seul niveau de répertoire ou pas de répertoire",
+            "upload_code_5019": "Il ne doit y avoir qu'un seul fichier obj et mtl",
+            "upload_code_5020": "La texture doit être contrôlée dans un délai de 1,5 Mo et le fichier obj doit être contrôlé dans 3M",
+            "upload_code_5012": "Les données sont anormales",
+            "upload_code_5023": "Le format du fichier de chargement est incorrect, <br> uniquement format jpg ou mp4.",
+            "download_tips": "Téléchargement {type}",
+            "download_fail": "Échec du téléchargement"
+        },
+        "vrhouse": {
+            "__name": "MaisonVR",
+            "linkto_management": "Aller à la gestion de MaisonVR"
+        },
+        "business": {
+            "__name": "Module Commerce",
+            "list_title": "Liste des hotspots de la Scène"
+        }
+    },
+    "login": {
+        "__name": "Identification",
+        "title": "Message",
+        "login": "Log in",
+        "logon": "Log in et continuer",
+        "login_tips": "Vous n'êtes pas connecté. Veuillez vous connecter pour modifier."
+    },
+    "common": {
+        "__name": "Publique",
+        "on": "on",
+        "off": "off",
+        "add": "Ajouter",
+        "set": "OK",
+        "ok": "OK",
+        "save": "Sauver",
+        "cancel": "Annuler",
+        "complete": "Compléter",
+        "edit": "Modifier",
+        "giveup": "Abandonner",
+        "commit": "Engager",
+        "photo": "Photo",
+        "voice": "Audio",
+        "video": "Vidéo",
+        "bgm": "Musique",
+        "crop": "Couper",
+        "upload": "Charger",
+        "download": "Télécharger",
+        "change": "Change",
+        "unnamed": "Sans nom",
+        "publish": "Sauver et publier",
+        "publish_text": "Sauvegarde et publication avec succès!",
+        "publish_tips": "Regarder votre scène immédiatement?",
+        "publish_btn_ok": "Maintenenant",
+        "publish_btn_no": "Plus tard",
+        "public": "Publique",
+        "private": "Privé",
+        "waiting": "Attente...",
+        "audio": "Audio",
+        "second": "s",
+        "delete": "Effacé",
+        "prev": "Retourner en arrière",
+        "next": "Next",
+        "meter": "≈{mètres}m",
+        "guide": "Visite Guidée",
+        "rule": "Règle",
+        "roaming": "Roaming",
+        "continue": "Continuer",
+        "ensure_delete": "Effacer",
+        "text_limit": "Limité á {limit} charactères",
+        "default": "Défaut",
+        "custom": "Personnaliser",
+        "back": "Retour",
+        "will_delete": "Sera supprimé",
+        "music": {
+            "__name": "Musique de fond",
+            "none": "Pas de musique",
+            "cheerfu": "Joyeux",
+            "ethereal": "Éthéré",
+            "rhythmic": "Rhythmique",
+            "nostalgic": "Nostalgique",
+            "missing": "Manquant",
+            "retro": "Rétro",
+            "strings": "Cordes",
+            "happy": "Heureux"
+        },
+        "category": {
+            "__name": "Catégorie",
+            "museum": "Musée",
+            "estate": "Immobilier",
+            "eshop": "E-Commerce",
+            "catering": "Restauration",
+            "home": "Maison",
+            "other": "Autre"
+        },
+        "uploads": {
+            "__name": "Téléchargement de fichiers",
+            "uploading": "Chargement",
+            "uploaded": "Téléchargé",
+            "wait": "Attendez...",
+            "error": "Erreur",
+            "add": "Ajouter",
+            "start": "Commencer",
+            "file_require": "Veuillez ajouter un fichier",
+            "cant_upload": "Votre navigateur ne prend pas en charge le chargement de fichiers",
+            "not_support": "Le fichier que vous avez sélectionné n'est pas {fileType} pris en charge par le navigateur, veuillez le sélectionner à nouveau",
+            "too_large": "Le fichier est trop grand et ne peut pas dépasser {size} trilliard",
+            "too_large_reupload_tips": "La vidéo chargée est trop large, veuillez charger à nouveau"
+        },
+        "tips": {
+            "__name": "Rapide",
+            "wait": "Veuillez attendre..."
+        }
+    },
+    "tips": {
+        "__name": "Rapide",
+        "title": "Conseil",
+        "set_done": "Configuration réussie",
+        "set_fail": "Échec de configuration",
+        "save_done": "Enregistrement réusssi",
+        "save_fail": "Échec d'enregistrement",
+        "delete": "Effacer ou pas?",
+        "delete_done": "Suppréssion réussie",
+        "delete_fail": "Échec de la suppréssion",
+        "upload_done": "Chargement réussi",
+        "upload_fail": "Échec du chargement",
+        "exception": "Erreur",
+        "network_error": "Internet est déconnecté, veuillez réessayer",
+        "file_notfound": "Le fichier est introuvable",
+        "scene_notfound": "La scène est introuvable",
+        "params_notfound": "Le paramètre est introuvable",
+        "camera_notfound": "La caméra correspondante est introuvable",
+        "password_error": "Mot de passe incorrect",
+        "data_error": "Les données sont introuvables",
+        "auth_deny": "Non autorisé à modifier la scène",
+        "clear": "Sûr d'effacer?",
+        "upload_pic_fail": "Échec du chargement des images",
+        "wait": "Veuillez attendre...",
+        "house_type_save_fail": "Échec de l'enregistrement du type de maison"
+    },
+    "show": {
+        "__name": "Afficher la page",
+        "on": "On",
+        "off": "Off",
+        "measure": "Outil de Mesure",
+        "measure_start": "Commencer",
+        "measure_end": "Terminer",
+        "measure_button": "Confirmer {status}",
+        "location_up": "Above",
+        "location_left": "Gauche",
+        "location_start_tips": "Veuillez clicker sur {direction}",
+        "location_end_tips": "Le point de départ est déterminé, veuillez localiser le point final",
+        "vr": "Mode VR",
+        "share": "Partager",
+        "music": "Allumer {status} musique",
+        "password_tips": "Mot de passe",
+        "password_require": "Veuillez entrer le mot de passe"
+    }
+}

+ 537 - 0
lang/_zh.ts

@@ -0,0 +1,537 @@
+export default {
+    "menu": {
+        "__name": "菜单",
+        "music": "背景音乐",
+        "base": "基础设置",
+        "information": "场景信息",
+        "screen": "初始画面",
+        "hotspot": "添加热点",
+        "guide": "自动导览",
+        "sign": "地面Logo",
+        "walk": "漫游可行",
+        "model": "修整模型",
+        "custom": "上传下载",
+        "videos": "添加视频",
+        "vrhouse": "看房 4Dkankan",
+        "business": "看店 4Dkankan",
+        "scene": "场景跳转",
+        "video": "添加视频",
+        "decor": "一键换装",
+        "link": "场景关联"
+    },
+    "modules": {
+        "__name": "模块",
+        "base": {
+            "__name": "基础设置",
+            "qrcode": "场景二维码",
+            "qrcode_download": "下载二维码",
+            "qrcode_tips": "自定义logo",
+            "scene_link": "场景地址",
+            "scene_link_copy": "复制链接",
+            "scene_link_copy_tips": "场景链接复制成功",
+            "bgm": "背景音乐",
+            "pano_text": "漫游视角可视",
+            "mode_2d_text": "平面图可视",
+            "mode_3d_text": "三维模型可视",
+            "map_text": "小地图预览可视",
+            "vr_text": "VR模式可视",
+            "vr_tips": "请在手机展示页面观看VR效果",
+            "guide_text": "自动导览可视",
+            "rule_text": "标尺可视",
+            "cad_text": "俯视图户型可视",
+            "measure_text": "测量工具可视",
+            "measure_tips": "编辑模式下无法使用测距功能,请在展示页面操作",
+            "turned_vr": "VR功能已{status}",
+            "turned_map": "小地图功能已{status}",
+            "turned_cad": "俯视图户型功能已{status}",
+            "turned_m2d": "平面视角功能已{status}",
+            "turned_m3d": "三维视角功能已{status}",
+            "turned_pano": "漫游视角功能已{status}",
+            "turned_rule": "标尺功能已{status}",
+            "turned_guide": "自动导览功能已{status}",
+            "turned_measure": "测距功能已{status}",
+            "shortcut_copy": "一键复制",
+            "share_link": "分享链接给好友",
+            "measure_show_tips": "请在展示页面使用测距功能",
+            "delete_measure_line": "删除测量线",
+            "please_click_tips": "'请点击“允许”'",
+            "vr_fail_app_tips": "浏览器未能检测到转动。请在手机或浏览器设置中开启了运动和方向访问等设置,然后刷新此页面。",
+            "vr_fail_safari_tips": "浏览器未能检测到转动。为完整体验VR效果,请打开 “设置” > “Safari” > “隐私和安全” 下的 “运动和方向访问” 开关,然后刷新此页面。",
+            "loading_bottom_text": "四维时代提供技术支持",
+            "vr_fail_reopen_tips": "运动和方向访问失败。您需要完全关闭此应用,然后再次打开,并允许访问运动与方向",
+            "add_music_title":"添加背景音乐",
+            "add_music_tips": "支持MP3、WAV等音频格式,不超过5MB",
+            "re_add_music": "重新添加",
+            "re_add_title": "重新添加背景音乐",
+            "re_add_tips": "新添加的音乐会替换已添加的音乐,<br>确定继续添加吗?",
+            "re_add_mobile_tips": "重新添加将会覆盖已添加音乐",
+            "delete_tips": "您确定删除当前音乐吗?",
+            "delete_title": "删除背景音乐",
+            "bgm_empty_tips": "请选择背景音乐",
+            "wechat": "微信",
+            "friend_circle": "朋友圈"
+        },
+        "information": {
+            "__name": "场景信息",
+            "title": "标题",
+            "title_tips": "请填写标题",
+            "title_require": "请添加标题({limit}字以内)",
+            "description": "简介",
+            "description_tips": "请填写简介",
+            "link": "添加链接",
+            "link_text_tips": "请填写链接文本",
+            "link_href_tips": "请填写链接地址",
+            "link_text_require": "请填写链接文本",
+            "link_href_require": "请填写链接地址",
+            "classify": "分类",
+            "upload_time": "上传时间",
+            "record": "未记录",
+            "shoot_count": "拍摄数量",
+            "password": "访问密码",
+            "password_tips": "访问密码",
+            "password_desc": "设置完密码后,当其他人访问您的场景时,需要输入您设置的密码才能访问。如无需设置点击“公开”即可。",
+            "password_require": "请输入{limit}位数的密码",
+            "logo_edit": "编辑页面Logo",
+            "logo_exit": "退出页面Logo编辑",
+            "logo_show_bottom": "显示初始Logo",
+            "logo_style1": "顶部Logo-方",
+            "logo_style2": "顶部Logo-长",
+            "logo_delete": "是否删除已上传Logo?"
+        },
+        "screen": {
+            "__name": "初始画面",
+            "current": "当前初始视角",
+            "current_set": "设置为初始画面",
+            "tips": "移动屏幕,点击保存您的初始画面。"
+        },
+        "hotspot": {
+            "__name": "添加热点",
+            "add": "添加热点",
+            "edit": "编辑热点",
+            "count": "已添加热点",
+            "location": "热点定位",
+            "location_tips": "将热点标记并拖动到合适的位置。",
+            "location_modify": "修改热点位置",
+            "location_confirm": "确定热点位置",
+            "location_desc": "请于左方两个场景区域拖动热点并对准所需标记的位置。",
+            "style": "选择热点样式",
+            "style_desc": "选择默认样式,或者手动上传图片自定义样式,上传图片格式PNG/JPG",
+            "style_name": "样式",
+            "style_dele": "是否删除该样式?",
+            "style_manage": "管理",
+            "style_exit": "退出",
+            "media_photo": "可添加图片以丰富热点内容",
+            "media_video": "可上传本地视频,进行更多的展示",
+            "media_voice": "可上传本地音频内容进行热点讲解",
+            "media_link": "可添加视频的超链接,视频将在热点里播放",
+            "title": "标题",
+            "title_tips": "请填写标题",
+            "title_require": "请添加标题({limit}字以内)",
+            "description": "简介",
+            "description_tips": "请填写简介",
+            "text_link": "添加链接",
+            "text_link_text_tips": "请填写链接文本",
+            "text_link_href_tips": "请填写链接地址",
+            "text_link_text_require": "链接文本不能为空",
+            "text_link_href_require": "链接地址不能为空",
+            "link": "嵌入式链接",
+            "link_require": "请添加外链",
+            "photo": "图片",
+            "voice": "音频",
+            "video": "视频",
+            "photo_tips": "支持JPG、PNG等图片格式,不超过{size}MB",
+            "voice_tips": "支持MP3、WAV等音频格式,不超过{size}MB",
+            "video_tips": "支持MP4、MOV等视频格式,不超过{size}MB",
+            "photo_require": "请添加图片",
+            "voice_require": "请添加音频",
+            "video_require": "请添加视频",
+            "m_location_tips1": "将上下分屏热点对准同一个所标记目标",
+            "m_location_tips2": "确定热点位置,点击下一步编辑内容",
+            "m_location_up": "上方",
+            "m_location_left": "左侧",
+            "m_location_move_tips": "辅助校准位置可能不准确,<br>请检查并拖动到{direction}相同位置",
+            "set_visible_btn": "设置热点可视",
+            "save_visible_btn": "保存当前设置",
+            "save_hotspot_done": "保存热点成功",
+            "save_hotspot_fail": "保存热点失败",
+            "delete_hotspot_tips": "是否删除当前热点?",
+            "delete_hotspot_done": "热点删除成功",
+            "delete_hotspot_fail": "热点删除失败",
+            "cant_add_hotspot_tips": "无法添加热点",
+            "cant_add_hotspot_content": "热点数目已达最大:{limit}",
+            "link_text_tips": "链接标题",
+            "link_href_tips": "链接",
+            "link_text_require": "请填写链接文本",
+            "link_href_require": "请填写链接地址",
+            "add_media": "添加多媒体"
+        },
+        "guide": {
+            "__name": "自动导览",
+            "route": "导航路线",
+            "view": "切换视角",
+            "record": "开始录制",
+            "record_audio": "录音",
+            "pause": "暂停",
+            "stop": "停止",
+            "end": "结束",
+            "delete": "删除",
+            "continue": "继续录制",
+            "preview": "预览",
+            "clear": "清空",
+            "sync": "声画同步",
+            "sound": "录制音频",
+            "file": "上传音频",
+            "file_add": "添加音频",
+            "tips": "点击开始录制导览",
+            "start": "开始",
+            "finish": "完成录制",
+            "less": "小于",
+            "replace_tips": "是否重新录制导览?",
+            "replace_content": "重新录制将覆盖之前的数据",
+            "sound_open_fail_tips": "麦克风开启失败",
+            "sound_tips": "麦克风开启失败,是否继续录制?",
+            "sound_content": "您需要在浏览器的设置中允许此网站使用麦克风,并且添加麦克风设备,然后刷新该页面。",
+            "upload_sound_done": "上传语音讲解成功",
+            "upload_sound_fail": "上传语音讲解失败",
+            "delete_sound_done": "删除语音讲解成功",
+            "delete_sound_fail": "删除语音讲解失败",
+            "room_title": "标题",
+            "room_title_tips": "请输入导览标题",
+            "room_title_require": "请输入标题",
+            "room_panel_title": "导览信息",
+            "room_sound_title": "录制音频",
+            "delete_video_content": "您当前录制的画面将会被删除",
+            "delete_file_content": "您当前上传的语音讲解将会被删除",
+            "delete_sound_content": "已录制配音将会被删除",
+            "camera_save_success": "镜头保存成功",
+            "clear_video_tip": "您当前录制的内容将会被清空",
+            "saving_sound": "正在保存录音",
+            "save_sound_done": "录音保存成功",
+            "save_sound_fail": "录音保存失败",
+            "no_sound_tips": "当前不支持录音<br>可在微信或电脑端录音",
+            "sound_success_tips": "麦克风开启成功",
+            "sound_fail_tips": "当前不支持录音<br>公众号信息配置错误",
+            "wechat_sound_fail_tips": "微信麦克风开启失败",
+            "open_sound_guide_tips": "可能您若要开启录音,请于微信的设置—隐私—授权管理中开启。且保证录音设备正常",
+            "sound_cant_open_tips": "此浏览器不支持录音。<br>建议更换其他主流浏览器,体验更佳",
+            "replace_sound_tips": "是否重新录制?",
+            "replace_sound_content": "已有语音讲解将会被替换",
+            "merge_sound_fail": "合并语音讲解失败",
+            "sound_limit": "录制时长为{time}分钟,当前已经达到上限",
+            "select_to_record": "请选择对应点位进行本区域的导览录制"
+        },
+        "sign": {
+            "title": "Logo样式",
+            "size": "Logo大小",
+            "style0": "样式一",
+            "style1": "样式二",
+            "style2": "样式三",
+            "style3": "手动上传"
+        },
+        "walk": {
+            "title": "漫游可行",
+            "tips1": "通过设置漫游可行,进一步优化在漫游时出现的体验;例如,您在漫游时,出现穿透房间的情况。",
+            "tips2": "通过点选各个漫游点的连线即可设置漫游点的可行性。",
+            "save": "保存当前设置",
+            "hide": "隐藏该点位置",
+            "show": "显示该点位置",
+            "pano_tips":"提示:您隐藏了初始画面点位,此操作将使进入场景后无法漫游。"
+        },
+        "model": {
+            "__name": "修整模型",
+            "cad": "CAD视角",
+            "cad_download": "平面图下载",
+            "view": "切换视角",
+            "title": "增添结构",
+            "title_door": "门窗类",
+            "title_component": "构建类",
+            "title_other": "其他",
+            "attribute": "属性",
+            "door": "门",
+            "slideDoor": "移门",
+            "casement": "窗",
+            "bayCase": "飘窗",
+            "groundCase": "落地窗",
+            "column": "柱子",
+            "furnColumn": "框架柱",
+            "furnFlue": "烟道",
+            "point": "点",
+            "line": "墙",
+            "tagging": "标注",
+            "tagging_name_tips": "请输入名称",
+            "tagging_area_tips": "输入面积,支持小数点后面两位",
+            "direction": "指南针",
+            "wallLine": "墙关联房间",
+            "widget_delete": "{widget}将被删除",
+            "panel_btn_default": "恢复默认",
+            "panel_btn_delete": "删除部件",
+            "attr_angle": "旋转角度",
+            "attr_within": "翻转方向",
+            "attr_ewidth": "宽度",
+            "attr_eheight": "高度",
+            "attr_tick": "厚度",
+            "attr_showTitle": "标注名称",
+            "attr_showContent": "标注面积",
+            "attr_top": "顶部",
+            "attr_bottom": "底部",
+            "success": "成功添加{widget}",
+            "error_location": "当前位置无法添加{widget}",
+            "error_outdoor": "户外无法添加{widget}",
+            "error_something": "当前位置点有建筑,无法添加{widget}",
+            "error_widget": "当前位置不可添加{widget}",
+            "enter_adjust_floor": "进入地面高度调节模式",
+            "exit_adjust_floor": "退出地面高度调节模式",
+            "color_title":"量角器颜色:"
+        },
+        "videos": {
+            "__name": "添加视频",
+            "tips": "请先点击场景的基准面,确定视频位置",
+            "panel_title": "视频属性",
+            "panel_preview": "预览",
+            "panel_upload": "上传视频",
+            "panel_upload_tips": "支持MP4视频格式",
+            "panel_move": "位移",
+            "panel_zoom": "缩放",
+            "panel_thickness": "厚度",
+            "recoverRatio": "恢复原始比例",
+            "recoverRatioTip": "恢复视频文件原始长宽比"
+        },
+        "custom": {
+            "model_title":"模型下载",
+            "uploading":"文件上传中...",
+            "download": "原始模型下载",
+            "download_panos": "下载点位图",
+            "upload": "修改模型上传",
+            "title1": "模型下载/上传功能教程",
+            "tips11": "1.下载场景模型的压缩包后,使用三维软件打开解压后文件“mesh.obj”,即可开展编辑;",
+            "tips12": "2.编辑完成后,建议将模型贴图进行烘焙,烘焙贴图需控制在1.5M以内,同时,保存的obj文件需要控制在3M以内;",
+            "tips13": "3.完成1、2步骤后,将obj,mtl,贴图打包为zip压缩包上传即可。",
+            "title2": "注:",
+            "tips21": "1.请尽量控制文件大小,以免浏览的时候卡顿,影响体验。",
+            "tips22": "2.请将obj及压缩包的名称保持一致,否则会替换失败。",
+            "get_image_fail": "获取贴图失败,可能网络状态不佳,请检查您的网络设置并重新尝试。",
+            "download_model_fail": "模型下载失败",
+            "reupload_tips": "上传后点击保存并发布生效",
+            "panoramic_upload": "全景照片上传",
+            "panoramic_upload_tips": "上传后点击“保存并发布”即生效",
+            "panoramic_upload_box_tips": "请上传对应点位名称的JPG图片",
+            "panoramic_download": "全景照片下载",
+            "panoramic_download_tips": "请勿修改照片名称和格式",
+            "panoramic": "全景图片",
+            "ball_video": "球幕视频",
+            "ball_video_upload_tips": "上传后点击“保存并发布”即生效",
+            "ball_video_download_tips": "请勿修改视频名称和格式",
+            "ball_video_upload_box_tips": "球幕视频支持MP4格式,不超过1024M",
+            "upload_title":"上传文件",
+            "upload_format_error": "文件格式错误,请重新上传",
+            "upload_name_error": "文件名称错误,请重新上传",
+            "upload_success": "上传成功,保存并发布后才能生效",
+            "upload_code_5017": "上传模型失败,请参照右侧教程",
+            "upload_code_5018": "zip文件只能有一层目录或无目录",
+            "upload_code_5019": "必须有且仅有一个obj和mtl文件",
+            "upload_code_5020": "贴图需控制在1.5M以内,obj文件需要控制在3M以内。",
+            "upload_code_5012": "数据不正常",
+            "upload_code_5023": "上传文件格式不正确,只能是jpg或mp4格式",
+            "download_tips": "下载后名称与格式请勿修改",
+            "download_fail": "下载失败"
+        },
+        "vrhouse": {
+            "__name": "VR看房",
+            "linkto_management": "前往云看房管理后台",
+            "refer": "刷新",
+            "scene_link": "场景链接"
+        },
+        "business": {
+            "__name": "商圈模块",
+            "list_title": "场景热点列表",
+            "linkto_management": "前往看店管理后台",
+            "refer": "刷新",
+            "scene_link": "场景链接"
+        },
+        "link":{
+            "__name": "跳转关联",
+            "title1":"锁定关联点",
+            "title2":"编辑关联点",
+            "title3":"全景图关联",
+            "list_text":" 已关联场景({l_count}), 全景图({p_count})",
+            "btn_new_text":"添加关联点",
+            "btn_add_title":"关联点定位",
+            "btn_add_text":"确定关联点位置",
+            "btn_add_desc":"将关联点标记并拖动到合适的位置。",
+            "btn_edit_text":"修改关联点位置",
+            "style": "关联点样式",
+            "style_name": "样式",
+            "style_desc": "支持自定义图标,建议尺寸为128x128 像素,格式为jpg/png。",
+            "style_dele": "是否删除该样式?<br />如该样式在其它关联点使用,也将被删除",
+            "desc_title":"描述",
+            "desc_tips":"请输入描述内容",
+            "desc_require":"请输入描述内容",
+            "type":"选择关联类型",
+            "type_photo": "支持上传宽高比为2:1的单张全景图片,建议像素不小于6000x3000像素,文件不超过120M。",
+            "type_photo_require":"请上传全景图",
+            "type_link": "请输入目标场景链接",
+            "type_link_tips": "http(s)://",
+            "type_link_require": "请输入场景链接",
+            "enter_title":"进入画面",
+            "enter_require":"请设置进入画面",
+            "enter_btn_text":"设置进入画面",
+            "outer_title":"跳出点位置",
+            "outer_style":"跳出点样式",
+            "outer_desc":"支持自定义图标,建议尺寸为128x128像素,格式为jpg/png。",
+            "outer_require":"请设置跳出点位置",
+            "outer_btn_text":"设置跳出点位置",
+            "links_title":"选择全景图",
+            "links_tips":"选择全景图,拖动到左侧画面相应位置,进行跳转设置。",
+            "links_cancel_tips":"取消关联设置"
+        }
+    },
+    "login": {
+        "__name": "登录",
+        "title": "提示",
+        "login": "去登录",
+        "logon": "登录完毕,继续",
+        "login_tips": "您没有登录,请于主页登录后再编辑"
+    },
+    "common": {
+        "__name": "公用",
+        "on": "打开",
+        "off": "关闭",
+        "add": "添加",
+        "set": "确定",
+        "ok": "确定",
+        "save": "保存",
+        "cancel": "取消",
+        "complete": "完成",
+        "edit": "修改",
+        "giveup": "放弃",
+        "commit": "提交",
+        "photo": "图片",
+        "voice": "音频",
+        "video": "视频",
+        "bgm": "音乐",
+        "crop": "裁剪",
+        "upload": "上传",
+        "download": "下载",
+        "change": "更改",
+        "unnamed": "未命名",
+        "publish": "保存并发布",
+        "publish_text": "保存并发布成功!",
+        "publish_tips": "是否立刻前往观看您的场景?",
+        "publish_btn_ok": "立刻前往",
+        "publish_btn_no": "暂时不去",
+        "public": "公开",
+        "private": "加密",
+        "waiting": "请稍等...",
+        "audio": "音频",
+        "second": "秒",
+        "delete": "删除",
+        "prev": "上一步",
+        "next": "下一步",
+        "meter": "约{meter}米",
+        "guide": "导览",
+        "rule": "测量工具",
+        "roaming": "漫游",
+        "continue": "继续",
+        "ensure_delete": "确定删除",
+        "text_limit": "限制{limit}字内",
+        "default": "默认",
+        "custom": "自定义",
+        "back": "返回",
+        "will_delete": "将被删除",
+        "setup":"设置",
+        "exit": "退出",
+        "music": {
+            "__name": "背景音乐",
+            "none": "无",
+            "cheerfu": "欢快",
+            "ethereal": "空灵",
+            "rhythmic": "节奏",
+            "nostalgic": "怀旧",
+            "missing": "想念",
+            "retro": "复古",
+            "strings": "琴弦",
+            "happy": "愉快"
+        },
+        "category": {
+            "__name": "分类",
+            "museum": "文博",
+            "estate": "地产",
+            "eshop": "电商",
+            "catering": "餐饮",
+            "home": "家居",
+            "other": "其他"
+        },
+        "uploads": {
+            "__name": "文件上传",
+            "uploading": "上传中",
+            "uploaded": "已上传",
+            "wait": "等待上传...",
+            "error": "上传出错",
+            "add": "添加文件",
+            "start": "开始上传",
+            "file_require": "请添加文件",
+            "cant_upload": "您的浏览器不支持上传文件",
+            "not_support": "您选择的不是浏览器支持的{fileType}文件,请重新选择",
+            "too_large": "文件过大,不能大于{size}MB",
+            "too_large_reupload_tips": "上传视频过大,请重新上传"
+        },
+        "tips": {
+            "__name": "提示",
+            "wait": "请等待..."
+        }
+    },
+    "tips": {
+        "__name": "弹窗、提示",
+        "title": "提示",
+        "set_done": "设置成功",
+        "set_fail": "设置失败",
+        "save_done": "保存成功",
+        "save_fail": "保存失败",
+        "delete": "是否删除?",
+        "delete_done": "删除成功",
+        "delete_fail": "删除失败",
+        "upload_done": "上传成功",
+        "upload_fail": "上传失败",
+        "exception": "异常错误",
+        "network_error": "网络连接失败,请稍后再试",
+        "file_notfound": "文件不存在",
+        "scene_notfound": "场景不存在",
+        "params_notfound": "缺少必要参数",
+        "camera_notfound": "找不到该场景对应的相机",
+        "password_error": "密码错误",
+        "data_error": "数据不正常",
+        "auth_deny": "无权操作该场景",
+        "clear": "是否清空?",
+        "upload_pic_fail": "上传图片失败",
+        "wait": "请稍后...",
+        "house_type_save_fail": "户型保存失败"
+    },
+    "show": {
+        "__name": "展示页面",
+        "on": "开启",
+        "off": "关闭",
+        "measure": "测距工具",
+        "measure_start": "起点",
+        "measure_end": "终点",
+        "measure_button": "确定{status}",
+        "location_up": "上方",
+        "location_left": "左侧",
+        "location_start_tips": "请先在{direction}点击",
+        "location_end_tips": "起点确定完毕,请定位终点",
+        "vr": "VR模式",
+        "share": "分享",
+        "music": "{status}音乐",
+        "password_tips": "密码",
+        "password_require": "请输入密码"
+    },
+    components:{
+        user_guid:{
+            "__name": "用户引导",
+            "title":"操作提示",
+            "pano":"行走<br />点击任意方向移动",
+            "rotate":"旋转视角<br />左右滑动屏幕,旋转视觉",
+            "zoom": "缩放<br />双指滑动放大或缩小视图",
+            "set":"我知道了"
+        }
+    }
+}

+ 13 - 0
lang/index.ts

@@ -0,0 +1,13 @@
+import zh from './_zh'
+
+const i18n = { 
+    t(api, obj) {
+        let vs = api.split('.')
+        let val = zh
+        while(vs.length) val = val[vs.shift()]
+        return val
+    }
+} as any
+
+
+export { i18n }

File diff suppressed because it is too large
+ 9112 - 0
package-lock.json


+ 48 - 0
package.json

@@ -0,0 +1,48 @@
+{
+  "name": "maptalks-three",
+  "version": "1.0.0",
+  "description": "",
+  "main": "index.js",
+  "scripts": {
+    "test": "echo \"Error: no test specified\" && exit 1",
+    "dev": "webpack-dev-server --mode development",
+    "build": "webpack --mode production"
+  },
+  "keywords": [],
+  "author": "",
+  "license": "ISC",
+  "devDependencies": {
+    "autoprefixer": "^9.1.5",
+    "copy-webpack-plugin": "^5.1.1",
+    "css-loader": "^1.0.0",
+    "dat-gui": "^0.5.0",
+    "extract-text-webpack-plugin": "^3.0.2",
+    "file-loader": "^1.1.11",
+    "html-webpack-plugin": "^3.2.0",
+    "postcss-cssnext": "^3.1.0",
+    "postcss-loader": "^3.0.0",
+    "style-loader": "^0.23.0",
+    "ts-loader": "^6.2.1",
+    "ts-polyfill": "^3.8.2",
+    "typescript": "^3.7.3",
+    "url-loader": "^1.1.1",
+    "webpack": "^4.17.2",
+    "webpack-cli": "^3.1.0",
+    "webpack-dev-server": "^3.1.8",
+    "worker-loader": "^2.0.0"
+  },
+  "dependencies": {
+    "axios": "^0.18.0",
+    "canvg": "^3.0.6",
+    "core-js": "^3.6.4",
+    "dat-gui": "^0.5.0",
+    "maptalks": "^0.41.1",
+    "maptalks.three": "^0.5.0",
+    "mathjs": "^5.10.0",
+    "ol": "^5.3.0",
+    "proj4": "^2.5.0",
+    "three": "^0.96.0",
+    "three-obj-loader": "^1.1.3",
+    "three-stats": "^1.0.1"
+  }
+}

+ 241 - 0
src/CAD/core/additional/calcRoom.js

@@ -0,0 +1,241 @@
+
+export default function calcRoom(data) {
+  let _roomsEdges = [];
+  let _roomsEdgesPoints = [];
+  let _roomsWalls = [];
+  let _roomsPoints = [];
+  let _roomsCenter = [];
+  let _roomsBoundingBox = [];
+  let _roomStartEdge = [];
+  let _roomMappings = [];
+  let _originRooms = [];
+  //分房间
+  //wallIds里的元素有三个属性:id,p1,p2,p1和p2对应的是墙端点的id
+  function getAllRooms(wallIds) {
+    for (let i = 0; i < wallIds.length; ++i) {
+      let exceptWalls = [];
+      let exceptPoints = [];
+      let ringPoints = [];   //存放的是顶点
+      let ringWalls = [];   //存放的是墙面
+      for (let k = 0; k <= i; ++k) {
+        exceptWalls.push(k);
+      }
+      let wall1 = wallIds[i];
+      let startPointId = wall1.p1;
+      let endPointId = wall1.p2;
+      ringPoints.push(endPointId);
+      ringWalls.push(wall1.id);
+      let currentPointId = null;
+      for (let j = i + 1; j < wallIds.length; ++j) {
+        let wall2 = wallIds[j];
+        if (startPointId == wall2.p1 || startPointId == wall2.p2) {
+          //相连
+          let _ringPoints = JSON.parse(JSON.stringify(ringPoints));
+          let _ringWalls = JSON.parse(JSON.stringify(ringWalls));
+          let _exceptWalls = JSON.parse(JSON.stringify(exceptWalls));
+          let _exceptPoints = JSON.parse(JSON.stringify(exceptPoints));
+          let _startPointId = startPointId;
+          _ringPoints.push(startPointId);
+          _ringWalls.push(wall2.id);
+          _exceptWalls.push(j);
+          if (_startPointId == wall2.p1) {
+            currentPointId = wall2.p2;
+          }
+          else {
+            currentPointId = wall2.p1;
+          }
+          _exceptPoints.push(_startPointId);
+          addLineToRoom(wallIds, endPointId, _ringPoints, _ringWalls, _exceptWalls, _exceptPoints, currentPointId);
+        }
+      }
+    }
+  }
+  //roomPoints房间的顶点
+  //roomWalls房间的墙
+  //exceptWalls不需要考虑的墙
+  //exceptPoints不需要考虑的端点
+  //currentPointId当前需要考虑的顶点,不在exceptPoints里
+  function addLineToRoom(wallIds, begin, roomPoints, roomWalls, exceptWalls, exceptPoints, currentPointId) {
+    for (let i = 0; i < wallIds.length; ++i) {
+      if (exceptWalls.indexOf(i) > -1 || exceptPoints.indexOf(currentPointId) > -1) {
+        continue;
+      }
+      let wall = wallIds[i];
+      let startPointId = wall.p1;
+      let endPointId = wall.p2;
+      if (currentPointId == startPointId || currentPointId == endPointId) {
+        let _roomPoints = JSON.parse(JSON.stringify(roomPoints));
+        let _roomWalls = JSON.parse(JSON.stringify(roomWalls));
+        let _exceptWalls = JSON.parse(JSON.stringify(exceptWalls));
+        let _exceptPoints = JSON.parse(JSON.stringify(exceptPoints));
+        let _currentPointId = currentPointId;
+        _roomWalls.push(wall.id);
+        _roomPoints.push(currentPointId);
+        _exceptWalls.push(i)
+        _exceptPoints.push(currentPointId);
+        if (_currentPointId == startPointId) {
+          _currentPointId = endPointId;
+        }
+        else if (_currentPointId == endPointId) {
+          _currentPointId = startPointId;
+        }
+        if (begin == _currentPointId) {
+          _roomsPoints.push(_roomPoints);
+          _roomsWalls.push(_roomWalls);
+        }
+        else {
+          addLineToRoom(wallIds, begin, _roomPoints, _roomWalls, _exceptWalls, _exceptPoints, _currentPointId)
+        }
+      }
+    }
+  }
+
+  //获取最小多边形
+  function removeBigPolygon() {
+    for (var i = 0; i < _roomsPoints.length; ++i) {
+      for (var j = i + 1; j < _roomsPoints.length; ++j) {
+        if (isInPolygonForPolygon(_roomsWalls[i], _roomsWalls[j], _roomsPoints[i], _roomsPoints[j])) {
+          _roomsPoints.splice(j, 1);
+          _roomsWalls.splice(j, 1);
+          --j;
+        }
+        else if (isInPolygonForPolygon(_roomsWalls[j], _roomsWalls[i], _roomsPoints[j], _roomsPoints[i])) {
+          _roomsPoints.splice(i, 1);
+          _roomsWalls.splice(i, 1);
+          --i;
+          --j;
+          break;
+        }
+      }
+    }
+  }
+  //arr1是否被arr2包围
+  function isInPolygonForPolygon(arrwalls1, arrwalls2, arrpoints1, arrpoints2) {
+    let arrpt2 = [];
+    for (let i = 0; i < arrpoints2.length; ++i) {
+      let point = arrpoints2[i];
+      arrpt2.push(point);
+    }
+    let flag = true;   //防止误杀回字形
+    for (var i = 0; i < arrpoints1.length; ++i) {
+      if (arrwalls2.indexOf(arrwalls1[i]) > -1) {
+        flag = false;   //存在重合的墙
+        continue;
+      }
+      let wall = arrwalls1[i];
+      var p1 = wall.p1;
+      var p2 = wall.p2;
+      var p3 = { x: (p1.x + p2.x) / 2, y: (p1.y + p2.y) / 2 };
+      if (isInPolygonForPoint(p1, arrpt2) && isInPolygonForPoint(p2, arrpt2) && isInPolygonForPoint(p3, arrpt2)) {
+        continue;
+      }
+      else {
+        return false;
+      }
+    }
+    if (flag) {
+      //可能是回字形了
+      //因为房间可能是多余的,要删除,所以只能先这么存储
+      let _roomMapping = [];
+      _roomMapping.push(arrwalls2[0]);
+      _roomMapping.push(arrwalls2[arrwalls2.length - 1]);
+      _roomMapping.push(arrwalls1[0]);
+      _roomMapping.push(arrwalls1[arrwalls1.length - 1]);
+      _roomMappings.push(_roomMapping);
+      return false;
+    }
+    else {
+      return true;
+    }
+  }
+  function isInPolygonForPoint(p, points) {
+    for (var i = 0; i < points.length; ++i) {
+      let point = points[i];
+      if (equalForPoint(p, point)) {
+        return true;
+      }
+    }
+    if (isPointInPoly(p, points)) {
+      return true;
+    }
+    else {
+      return false;
+    }
+  }
+  //找房间包含关系,因为接下来房间内,墙的顺序会改
+  function getIncludeFromRoom() {
+    let mappings = [];
+    for (let i = 0; i < _roomMappings.length; ++i) {
+      let _roomMapping = _roomMappings[i];
+      let mapping = [];
+      mapping[0] = null;
+      mapping[1] = null;
+      for (let j = 0; j < _roomsWalls.length; ++j) {
+        if (_roomMapping[0] == _roomsWalls[j][0] && _roomMapping[1] == _roomsWalls[j][_roomsWalls[j].length - 1]) {
+          mapping[0] = j;
+        }
+        else if (_roomMapping[2] == _roomsWalls[j][0] && _roomMapping[3] == _roomsWalls[j][_roomsWalls[j].length - 1]) {
+          mapping[1] = j;
+        }
+        if (mapping[0] != null && mapping[1] != null) {
+          break;
+        }
+      }
+      if (mapping[0] != null && mapping[1] != null) {
+        mappings.push(mapping);
+      }
+    }
+    _roomMappings = mappings;
+  }
+
+  function equalForPoint(p1, p2) {
+    if (p1.x == p2.x && p1.y == p2.y) {
+      return true;
+    }
+    else {
+      return false;
+    }
+  }
+  function isPointInPoly(point, points) {
+    var x = point.x, y = point.y;
+    var inside = false;
+    for (var i = 0, j = points.length - 1; i < points.length; j = i++) {
+      var pt1 = points[i];
+      var pt2 = points[j];
+      var xi = pt1.x, yi = pt1.y;
+      var xj = pt2.x, yj = pt2.y;
+      var intersect = ((yi > y) != (yj > y))
+        && (x < (xj - xi) * (y - yi) / (yj - yi) + xi);
+      if (intersect) inside = !inside;
+    }
+    return inside;
+  }
+
+  // 将wall p1 p2字段转化为真实点坐标
+  let wall = data.wall.map(line => {
+    return {
+      p1: data.vertex.find(({id}) => line.p1 === id),
+      p2: data.vertex.find(({id}) => line.p2 === id),
+      id: line.id
+    }
+  })
+
+
+  getAllRooms(wall)
+  
+  // 将_roomsWalls所有的p1 p2转化为真实坐标
+  _roomsWalls = _roomsWalls.map(walls => {
+    return walls.map(cid => wall.find(({id}) => cid === id))
+  })
+
+  removeBigPolygon()
+  getIncludeFromRoom()
+
+  data.room = _roomsWalls.map((wall, i) => {
+    return {
+      wall: wall.map(({id}) => id),
+      ground: _roomsPoints[i].map(({id}) => id),
+    }
+  })
+  return data
+}

+ 272 - 0
src/CAD/core/additional/dataHandle.ts

@@ -0,0 +1,272 @@
+import { AttachCAD as CAD } from '../index'
+import { Data, _Point, _Line, _window, _door, _column } from '../base/processing/index'
+import Stack from '../base/stack'
+import Point from '../core/point'
+import Case from '../architecture/casement'
+import Door from '../architecture/door/index'
+import Column from '../architecture/column'
+import GroundCase from '../architecture/groundCase'
+import SlideDoor from '../architecture/slideDoor'
+import BayCase from '../architecture/bayCase'
+import FurnColumn from '../furniture/column'
+import FurnFlue from '../furniture/flue'
+import { debounce } from '../util'
+import Line from '../core/line'
+import wallline from '../core/wallline'
+import calcRoom from './calcRoom'
+
+let notSave = false
+
+export interface transformRet {
+  getData: (isCalcRoom?: boolean) => Data
+  loadData: (data: Data) => void
+}
+
+type stackStatus = { previous: boolean, next: boolean }
+export interface stackRet {
+  getStackState: () => stackStatus
+  preservation: () => void
+  previous: () => stackStatus
+  next: () => stackStatus
+  clearStack: () => void
+}
+
+
+// 兼容V2
+const compatiblev2 = (data: any): Data => {
+  data.column = data.column || []
+  data.window = data.window || []
+  data.door = data.door || []
+  data.groundCase = data.groundCase || []
+  data.bayCase = data.bayCase || []
+  data.slideDoor = data.slideDoor || []
+  data.tagging = data.tagging || []
+  data.furnColumn = data.furnColumn || []
+  data.furnFlue = data.furnFlue || []
+
+  let eles = ['column', 'window', 'door', 'groundCase', 'bayCase', 'slideDoor', 'tagging', 'furnColumn', 'furnFlue']
+  if (data['vertex-xy']) {
+    data.vertex = data['vertex-xy']
+    data.wall = data['segment'].map(item => ({ id: item.id, p1: item.a, p2: item.b, border: item.border }))
+  }
+
+
+  for (let i = 0; i < data.wall.length; i++) {
+    let checkWalls = data.wall.filter(item => item !== data.wall[i])
+
+    let so = checkWalls.find(({ p1, p2 }) => {
+      return (p1 === data.wall[i].p1 && p2 === data.wall[i].p2) ||
+        (p2 === data.wall[i].p1 && p1 === data.wall[i].p2)
+    })
+
+
+    if (so) {
+      eles.forEach(attr => {
+        data[attr].forEach((item) => {
+          if (item.line === data.wall[i].id) {
+            item.line = so.id
+          }
+        })
+      })
+      data.wall.splice(i--, 1)
+    }
+  }
+
+  data.vertex = data.vertex.filter(p => data.wall.some(({ p1, p2 }) => p.id === p1 || p.id === p2))
+  return data
+}
+
+// 反转cad图所有点的Y轴 因为cad与三维Y方向不一样
+export const roateDataY = (data: Data): Data => {
+  data = {
+    ...data,
+    vertex: data.vertex.map(p => ({ ...p, y: -p.y })),
+    window: [...data.window],
+    column: [...data.column],
+    door: [...data.door],
+    groundCase: [...data.groundCase],
+    slideDoor: [...data.slideDoor],
+    bayCase: [...data.bayCase],
+    tagging: [...data.tagging],
+    furnColumn: [...data.furnColumn],
+    furnFlue: [...data.furnFlue]
+  };
+
+  [data.window, data.column, data.door, data.slideDoor, data.tagging, data.groundCase, data.bayCase, data.furnColumn, data.furnFlue].forEach((eles) => {
+    eles.forEach((ele, i) => {
+      eles[i] = {
+        ...ele,
+        // 反转Y
+        pos: ele.pos.map((p, i) => (i % 2) ? -p : p)
+      }
+    })
+  })
+  return data
+}
+
+
+// 附加数据与svg转换功能
+export const attchTransform = (cad: CAD) => {
+  const processing = cad.processing
+
+  // 获取数据
+  cad.getData = (isCalcRoom = false): Data => {
+    let data = roateDataY(processing.toData())
+    data.dire = cad.direction.angle
+
+    if (isCalcRoom) {
+      data = calcRoom(data)
+      data.room.forEach(({ground}) => {
+        console.log(ground.map(point => cad.processing.points.find(({id}) => id === point).ele.real))
+      })
+    }
+
+    return data
+  }
+  // 加载数据
+  cad.loadData = (data: Data) => {
+    notSave = true
+    // 卸载旧数据
+    processing.attrs.forEach(attr => {
+      while (processing[attr].length) {
+        // 同步删除
+        try {
+          processing[attr][0].ele.destroy(true, true, false)
+        } catch {
+        }
+      }
+    })
+
+
+    data = roateDataY(compatiblev2(data))
+
+    // 多余的剔除
+    data.surplus = []
+
+    processing.toEles(data)
+    cad.direction.angle = data.dire || 0
+    cad.adapt(data)
+    setTimeout(() => notSave = false, 5000)
+  }
+}
+
+// 附加栈功能
+export const attchStack = (cad: CAD) => {
+  sessionStorage.clear();
+
+  let current = 0
+  let stack = new Stack('cad_' + Date.now())
+
+  const getRet = (c = current): stackStatus => {
+    let slen = stack.getLength()
+    let previous = slen === 1 && current === 1 ? false : c > 0
+    return { previous: previous, next: c < slen - 1 }
+  }
+
+  cad.clearStack = () => {
+    sessionStorage.clear()
+    stack.setLength(current = 0)
+  }
+
+
+  // 保存
+  cad.preservation = debounce(() => {
+    if ((cad as any).stoppreservation) return;
+
+    // if (notSave) return;
+    let newData = cad.getData()
+    let prevIndex = stack.getLength() - 1
+
+    // 旧版数据,先注释
+    // newData["vertex-xy"] = newData["vertex-xy"].concat(newData.surplus)
+    delete newData.surplus
+
+    if (prevIndex === 0 ||
+      JSON.stringify(newData) !== JSON.stringify(stack.get(prevIndex))
+    ) {
+      // stack.setLength(current + 1)
+
+
+      stack.push(newData)
+      ++current
+    }
+  }, 500)
+
+  let minStep = 500
+  let currDate = Date.now()
+  let timeout
+
+  // 撤销
+  cad.previous = (): stackStatus => {
+    if (!getRet().previous) return getRet()
+    --current
+    clearTimeout(timeout)
+    timeout = setTimeout(() => {
+      // console.log(stack.get(current))
+      cad.loadData(stack.get(current))
+    }, minStep)
+
+    return getRet()
+  }
+
+  // 回退
+  cad.next = (): stackStatus => {
+    if (!getRet().next) return getRet()
+    ++current
+    clearTimeout(timeout)
+    timeout = setTimeout(() => {
+      cad.loadData(stack.get(current))
+    }, minStep)
+
+    return getRet()
+
+    if (Date.now() - currDate > minStep) {
+      currDate = Date.now()
+      getRet().next && cad.loadData(stack.get(++current))
+    }
+    return getRet()
+  }
+
+  cad.getStackState = getRet
+
+  let destroy = cad.destroy
+  cad.destroy = function (...args) {
+    stack = null
+    destroy.apply(this, args)
+  }
+}
+
+// 附加自动保存功能
+export const autoPreservation = (cad: CAD) => {
+
+  [Point, Case, Door, Column, GroundCase, SlideDoor, BayCase, FurnColumn, Line, FurnFlue, wallline].forEach(cls => {
+
+    let dragend = cls.prototype.dragEnd
+    cls.prototype.dragEnd = async function (...args) {
+      dragend && await dragend.call(this, ...args)
+      setTimeout(() => {
+        cad.preservation()
+      }, 100)
+    }
+
+    let destroy = cad.destroy
+    cad.destroy = function (...args) {
+      destroy.apply(this, args)
+      cls.prototype.dragEnd = dragend
+    }
+  });
+
+  const increase = cad.increase
+  cad.increase = (...args) => {
+    let ret = increase.call(cad, ...args)
+
+    if (!ret.error) {
+      setTimeout(() => {
+        cad.preservation()
+      }, 100)
+    }
+    return ret
+  }
+
+  cad.preservation()
+}

+ 53 - 0
src/CAD/core/additional/disabled.ts

@@ -0,0 +1,53 @@
+import {AttachCAD as CAD} from '../index'
+import { CADElement } from '../core/element'
+import LineArch from '../architecture/linearch'
+import Column from '../architecture/column'
+
+export interface Disabled {
+  forbidden: Function
+  available: Function
+}
+
+// 启用或禁用cad
+export const attachDisabled = (cad: CAD) => {
+  let editApis = ['increase', 'getStackState', 'preservation', 'previous', 'next', 'closeMouseHandle', 'showGauge', 'hideGauge']
+  let apiFuns = []
+
+  // 禁用cad所有交互取消
+  cad.forbidden = () => {
+    if (apiFuns.length === editApis.length) return;
+    
+    cad.hideGauge()
+    cad.closeMouseHandle()
+
+    CADElement.examples.get(cad.processing.render).forEach(e => e.unEvent())
+
+    LineArch.attaArch.forEach(archs => {
+      archs.forEach(arch => {
+        arch instanceof Column && arch.delEvent()
+      })
+    })
+
+    editApis.forEach(key => {
+      apiFuns.push(cad[key])
+      delete cad[key]
+    })
+  }
+
+  // 启用cad激活操作
+  cad.available = () => {
+    if (apiFuns.length !== editApis.length) return;
+
+    editApis.forEach((key, i) => cad[key] = apiFuns[i])
+    apiFuns = []
+
+    CADElement.examples.get(cad.processing.render).forEach(e => e.listen());
+    
+    LineArch.attaArch.forEach(archs => {
+      archs.forEach(arch => arch instanceof Column && arch.addEvent())
+    })
+
+    cad.showGauge()
+    cad.openMouseHandle()
+  }
+}

+ 420 - 0
src/CAD/core/additional/eleAttr.ts

@@ -0,0 +1,420 @@
+import {AttachCAD} from '../index'
+import Column from '../architecture/column'
+import Door from '../architecture/door/index'
+import Casement from '../architecture/casement'
+import Arch from '../architecture/linearch'
+import FurnColumn from '../furniture/column'
+import {FurnClass as Furn} from '../furniture/furn'
+import WallLine from '../core/wallline'
+import Point from '../core/point'
+
+import {
+  lineDis,
+  lineCenter,
+  getDisPointLinePoints,
+  segmentsIntrFine,
+  isContainPoint,
+  Line,
+  lineStretch
+} from '../geometry'
+import SlideDoor from '../architecture/slideDoor'
+
+// 拦截arch门窗柱子墙线条添加方法, 添加额外属性
+export default (cad: AttachCAD) => {
+
+  let addCase = cad.processing.addCase
+  let addDoor = cad.processing.addDoor
+  let addColumn = cad.processing.addColumn
+  let addLine = cad.processing.addLine
+  let addSlideDoor = cad.processing.addSlideDoor
+  let addFurnColumn = cad.processing.addFurnColumn
+  let addFurnFlue = cad.processing.addFurnFlue
+
+  // 添加与宽度相关的属性
+  let addWidthAttr = (ele: Column | Door | Casement | SlideDoor ) => {
+    Object.defineProperties(ele, {
+      // 修改宽度
+      ewidth: {
+        get: () => Number(lineDis({points: ele.linePoints}).toFixed(2)),
+        set: (val) => {
+          if (val <= 0 || val === ele.ewidth) return;
+          let center = lineCenter({points: ele.linePoints})
+          let [p1, p2] = getDisPointLinePoints(ele.attachment, center, val / 2)
+
+          if (lineDis({points: [ele.linePoints[0], p1]}) > lineDis({points: [ele.linePoints[0], p2]})) {
+            ele.linePoints[0].x = p2.x
+            ele.linePoints[0].y = p2.y
+            ele.linePoints[1].x = p1.x
+            ele.linePoints[1].y = p1.y
+          } else {
+            ele.linePoints[0].x = p1.x
+            ele.linePoints[0].y = p1.y
+            ele.linePoints[1].x = p2.x
+            ele.linePoints[1].y = p2.y
+          }
+        }
+      },
+      // 最大宽度
+      maxWidth: {
+        get: () => {
+          let archs = [...Arch.attaArch.get(ele.attachment), ele.attachment]
+          let p1MinPoints: Array<{dis: number, point: Point}> = []
+          let p2MinPoints: Array<{dis: number, point: Point}> = []
+          let item = (arch: any, i, j) : {dis: number, point: Point} => {
+            let points = arch.linePoints || arch.points
+            return {
+              dis: lineDis({points: [ele.linePoints[i], points[j]]}), 
+              point: points[j]
+            }
+          }
+
+          archs.forEach(arch => {
+            if (arch !== ele) {
+              p1MinPoints.push(item(arch, 0, 0))
+              p1MinPoints.push(item(arch, 0, 1))
+              p2MinPoints.push(item(arch, 1, 0))
+              p2MinPoints.push(item(arch, 1, 1))
+            }
+          })
+
+          p1MinPoints.sort((a, b) => a.dis - b.dis)
+          p2MinPoints.sort((a, b) => a.dis - b.dis)
+          
+          let points = (p1MinPoints[0].dis > p2MinPoints[0].dis ?
+            [ele.linePoints[1], p2MinPoints[0].point] :
+            [ele.linePoints[0], p1MinPoints[0].point]) as [Point, Point];
+
+          
+          return Number((lineDis({points}) * 2 + lineDis({points: ele.linePoints})).toFixed(2))
+        }
+      },
+      minWidth: {
+        get: () => 0.2
+      }
+    })
+  }
+
+  // 为柱子添加厚度的属性
+  let addTickAttr = (ele: Column) => {
+    Object.defineProperties(ele, {
+      tick: {
+        get: () => Math.max(
+          Number(lineDis({points: [ele.points[0], ele.points[3]]}).toFixed(2)),
+          Number(lineDis({points: [ele.points[1], ele.points[2]]}).toFixed(2)),
+        ),
+        set: (val) => {
+          if (val <= 0 || val === ele.oldVal) return;
+          ele.oldVal = val
+          ele.thickness = val
+          ele.updatePeripheral()
+        }
+      },
+      maxTick: {
+        get() {
+          let line1 = {points: [ele.points[0], ele.points[3]]} as {points: [Point, Point]}
+          let line2 = {points: [ele.points[1], ele.points[2]]} as {points: [Point, Point]}
+          let lines = cad.processing.lines.map(line => line.ele)
+          let j1points = []
+          let j2points = []
+
+          for (let i = 0; i < lines.length; i++) {
+            if (lines[i] === ele.attachment) continue;
+
+            let point1 = segmentsIntrFine(lines[i], line1)
+            let point2 = segmentsIntrFine(lines[i], line2)
+
+            if (point1 && isContainPoint(lines[i], point1)) {
+              j1points.push(point1)
+            }
+            if (point2 && isContainPoint(lines[i], point2)) {
+              j2points.push(point2)
+            }
+          }
+
+          j1points.sort((a, b) => lineDis({points: [ele.points[0], a]}) - lineDis({points: [ele.points[0], b]}))
+          j2points.sort((a, b) => lineDis({points: [ele.points[1], a]}) - lineDis({points: [ele.points[1], b]}))
+
+          let minp1len = 0;
+          for(let i = 0; i < j1points.length; i++) {
+            if ( isContainPoint({points: [ele.points[0], j1points[i]]}, ele.points[3]) ||
+            isContainPoint({points: [ele.points[0], ele.points[3]]}, j1points[i]) ) {
+              minp1len = lineDis({points: [ele.points[0], j1points[i]]})
+              break;
+            }
+          }
+          let minp2len = 0;
+          for(let i = 0; i < j2points.length; i++) {
+            if ( isContainPoint({points: [ele.points[1], j2points[i]]}, ele.points[2]) ||
+            isContainPoint({points: [ele.points[1], ele.points[2]]}, j2points[i]) ) {
+              minp2len = lineDis({points: [ele.points[1], j2points[i]]})
+              break;
+            }
+          }
+
+          let len = !minp1len ? minp2len : 
+            !minp2len ? minp1len :
+              minp1len < minp2len ? minp1len : minp2len
+
+
+          return Number((len).toFixed(2)) || 10
+        }
+      },
+      minTick: {
+        get: () => 0.2
+      }
+    })
+  }
+
+  // 为门窗添加高度的属性
+  let addHeightAttr = (ele: Column | Door | SlideDoor ) => {
+    Object.defineProperties(ele, {
+      eheight: {
+        get: () => {
+          return Number((ele.top - ele.bottom).toFixed(2))
+        },
+        set: (val) => {
+          ele.in3D.changeHeight(val)
+        }
+      },
+      maxHeight: {
+        get: () => {
+          let rooms = cad.processing.getRoomsByLine(ele.attachment)
+
+          if (rooms.length) {
+            return Number((rooms[0].top - rooms[0].bottom).toFixed(2))
+          } else {
+            return 5
+          }
+        }
+      },
+      minHeight: {
+        get: () => 0.2
+      }
+    })
+  }
+
+  // 为线条添加修改高度的属性
+  let addTopAttr = (ele: WallLine) => {
+    Object.defineProperties(ele, {
+      top: {
+        get: () => {
+          let hole = cad.processing.getHolesByLine(ele)[0]
+          if (hole) {
+            return hole.top
+          } else {
+            let room = cad.processing.getRoomsByLine(ele)[0]
+            return room.top
+          }
+        },
+        set: (val) => {
+          val !== ele.top && ele.updateRoomTop(val)
+        }
+      },
+      maxTop: {
+        get: () => 10
+      },
+      minTop: {
+        get: () => 0.5
+      }
+    })
+  }
+
+  // 最线条添加修改底边的属性
+  let addBottomAttr = (ele: WallLine) => {
+    Object.defineProperties(ele, {
+      bottom: {
+        get: () => {
+          let room = cad.processing.getRoomsByLine(ele)[0]
+          if (room) {
+            return room.bottom
+          } else {
+            let hole = cad.processing.getHolesByLine(ele)[0]
+            return hole.bottom
+          }
+        },
+        set: (val) => {
+          console.error('-------------')
+          if (val !== ele.bottom) {
+            let lines = cad.processing.lines.filter(({ele: cele}) => ~cele.points.indexOf(ele.points[0]) && ~cele.points.indexOf(ele.points[1]))
+            // let rooms = lines.map(({room}) => room)
+            console.error(lines)
+            lines.forEach(({ele}) => ele.updateRoomBottom(val))
+          }
+        }
+      },
+      minBottom: {
+        get: () =>  -2.5
+      },
+      maxBottom: {
+        get: () => -0.5
+      }
+    })
+  }
+
+  let addFurnTopAttr = (ele: Furn) => {
+    Object.defineProperties(ele, {
+      top: {
+        get: () => ele.top,
+        set: (val) => {
+          ele.top = val
+        }
+      },
+      maxTop: {
+        get: () => ele.room.top
+      },
+      minTop: {
+        get: () => 0.5
+      }
+    })
+  }
+
+  let addFurnBottomAttr = (ele: Furn) => {
+    Object.defineProperties(ele, {
+      bottom: {
+        get: () => ele.bottom,
+        set: (val) => {
+          ele.bottom = val
+        }
+      },
+      maxTop: {
+        get: () => ele.room.bottom
+      },
+      minTop: {
+        get: () => -0.5
+      }
+    })
+  }
+
+
+  let addFurnWidthAttr = (ele: Furn) => {
+    Object.defineProperties(ele, {
+      // 修改宽度
+      ewidth: {
+        get: () => Number(lineDis({points: [ele.points[0], ele.point[1]]}).toFixed(2)),
+        set: (val) => {
+          if (val <= 0 || val === ele.ewidth) return;
+          let newLine1 = lineStretch( { points: [ele.points[0], ele.points[1]] }, val )
+          let newLine2 = lineStretch( { points: [ele.points[2], ele.points[3]] }, val )
+          let points = newLine1.points.concat(newLine2.points)
+
+          if (ele.check(points)) {
+            ele.stop = false
+            ele.points.forEach((point, i) => {
+              point.x = points[i].x
+              point.y = points[i].y
+            })
+            ele.nextTick(() => ele.stop = true)
+          }
+        }
+      },
+      // 最大宽度
+      maxWidth: {
+        get: () => {
+          return 10
+        }
+      },
+      minWidth: {
+        get: () => 0.25
+      }
+    })
+  }
+
+  let addFurnTickAttr = (ele: Furn) => {
+    Object.defineProperties(ele, {
+      tick: {
+        get: () => Number(lineDis({points: [ele.points[1], ele.point[2]]}).toFixed(2)),
+        set: (val) => {
+          if (val <= 0 || val === ele.tick) return;
+          let newLine1 = lineStretch( { points: [ele.points[0], ele.points[3]] }, val )
+          let newLine2 = lineStretch( { points: [ele.points[1], ele.points[2]] }, val )
+          let points = [newLine1.points[0], newLine2.points[0], newLine2.points[1], newLine1.points[1]]
+
+          if (ele.check(points)) {
+            ele.stop = false
+            ele.points.forEach((point, i) => {
+              point.x = points[i].x
+              point.y = points[i].y
+            })
+            ele.nextTick(() => ele.stop = true)
+          }
+        }
+      },
+      // 最大宽度
+      maxTick: {
+        get: () => {
+          return 10
+        }
+      },
+      minTick: {
+        get: () => 0.25
+      }
+    })
+  }
+
+  
+  cad.processing.addCase = function(...args) {
+    let ret = addCase.call(this, ...args)
+    addWidthAttr(ret.ele)
+    addHeightAttr(ret.ele)
+    return ret
+  }
+
+  cad.processing.addDoor = function(...args) {
+    let ret = addDoor.call(this, ...args)
+    addWidthAttr(ret.ele)
+    addHeightAttr(ret.ele)
+    return ret
+  }
+  
+  cad.processing.addSlideDoor = function(...args) {
+    let ret = addSlideDoor.call(this, ...args)
+    addWidthAttr(ret.ele)
+    addHeightAttr(ret.ele)
+    return ret
+  }
+
+  cad.processing.addColumn = function(...args) {
+    let ret = addColumn.call(this, ...args)
+    addWidthAttr(ret.ele)
+    addTickAttr(ret.ele)
+    return ret
+  }
+
+  cad.processing.addLine = function(...args) {
+    let ret = addLine.call(this, ...args)
+    addTopAttr(ret.ele)
+    addBottomAttr(ret.ele)
+    return ret
+  }
+
+  
+  cad.processing.addFurnColumn = function(...args) {
+    let ret = addFurnColumn.call(this, ...args)
+    addFurnTopAttr(ret.ele)
+    addFurnBottomAttr(ret.ele)
+    addFurnWidthAttr(ret.ele)
+    addFurnTickAttr(ret.ele)
+    return ret
+  }
+
+  cad.processing.addFurnFlue = function(...args) {
+    let ret = addFurnFlue.call(this, ...args)
+    addFurnTopAttr(ret.ele)
+    addFurnBottomAttr(ret.ele)
+    addFurnWidthAttr(ret.ele)
+    addFurnTickAttr(ret.ele)
+    return ret
+  }
+
+  
+  let destroy = cad.destroy
+  cad.destroy = function(...args) {
+    addCase = null
+    addDoor = null
+    addColumn = null
+    addLine = null
+    addFurnFlue = null
+    addFurnColumn = null
+    destroy.apply(this, args)
+  }
+}

+ 348 - 0
src/CAD/core/additional/insert.ts

@@ -0,0 +1,348 @@
+import {Screen} from '../base/cad'
+import {AttachCAD as CAD} from '../index'
+import WallLine from '../core/wallline'
+import LineArch from '../architecture/linearch'
+import LinePoint from '../core/point'
+import Furn from '../furniture/furn'
+import {type as Type} from '../util'
+import {
+  Point,
+  pointLineDis,
+  getLinePoint,
+  isContainPoint,
+  getDisPointLinePoints,
+  getDisVerticalLinePoints,
+  lineDis,
+  pointInside,
+  segmentsIntr,
+  Line,
+  isFaceIntersect,
+  isFaceContain
+} from '../geometry'
+import {
+  TAGGING,
+  POINT, DOOR, COLUMN, CASEMENT, ARCH, SLIDEDOOR,  GROUNDCASE, BAYCASE,
+  FURNCOLUMN, FURNFLUE, DEFAULT, LINE, SEFTLINE
+} from '../constant/Element'
+import { i18n } from '../../../../lang/index'
+
+const ONE = [TAGGING, LINE, SEFTLINE]
+const ATTACHCOM = [POINT, DOOR, COLUMN, CASEMENT, ARCH, SLIDEDOOR, GROUNDCASE, BAYCASE]
+const STANDCOM = [FURNCOLUMN, FURNFLUE]
+
+export interface insertRet {error: boolean, msg: string, obj?: any}
+export interface insert{
+  increase: (type: 'point' | 'door' | 'column' | 'casement' | 'tagging' | 'seftline', pos?: Screen | any, top?: number | null, bottom?: number | null) => insertRet
+}
+
+export const attachInsert = (cad: CAD) => {
+  let processing = cad.processing
+  let renderer = processing.render
+
+  // 从一条线段指定位置中获取建筑两端端点
+  const getArchLinePoints = (line: WallLine, point: Point, width: number) : string | [Point, Point] => {
+    let [p1, p2] = getDisPointLinePoints(line, point, width / 2)
+    return [p1, p2]
+  }
+
+  // 计算添加所需要的参数
+  const calcAttachComArgs = (rpoint: Point, type) => {
+    let lines = processing.lines.map(({ele}) => ele)
+        .filter(line => !line.exterior)
+        .map(line => ({ dis: pointLineDis(line, rpoint), line }))
+        // .filter(line => !line.line.isOut || type === POINT)
+        .sort(( {dis: dis1}, {dis: dis2} ) =>  dis1 - dis2)   // 获取距离激活位置所有线条,并排序
+
+      let incrObj = lines.find(ld => 
+        pointLineDis(ld.line, getLinePoint(ld.line, {x: rpoint.x})) < 0.1 || 
+        pointLineDis(ld.line, getLinePoint(ld.line, {y: rpoint.y})) < 0.1
+      )  // 获取应添加在哪个线段
+
+      if (!incrObj) {
+        return {
+          error: true,
+          msg: i18n.t('modules.model.error_location', {widget: ARCH[type]}),
+          obj: null
+        }
+      }
+      
+      let incrLine = incrObj.line
+      let lineEle = processing.lines.find(({ele}) => ele === incrLine)
+
+
+      let archs = LineArch.attaArch.get(incrLine)
+      // 转化为在线条中的点
+      let point = getLinePoint(incrLine, rpoint)
+
+      if (type === POINT){
+        if (pointLineDis(incrObj.line, point) > 0.1) {
+          return {
+            error: true,
+            msg: i18n.t('modules.model.error_location', { widget: ''}),
+            obj: null
+          } 
+        } else {
+          return [incrLine, point]
+        }
+      } 
+      else {
+        let points = getArchLinePoints(incrLine, point, DEFAULT[type].width)
+        if (!Type.isArray(points)) {
+          return {error: true, msg: (points as string)}
+        } else {
+          return [incrLine, points, rpoint]
+        }
+      }
+  }
+
+  const calcStandComArgs = (rpoint: Point, type) => {
+    const width = DEFAULT[type].width
+    const tick = DEFAULT[type].tick
+    const splitWidth = width / 2
+    const splitTick = tick / 2
+    const points = [
+      {x: rpoint.x - splitWidth, y: rpoint.y + splitTick},
+      {x: rpoint.x + splitWidth, y: rpoint.y + splitTick},
+      {x: rpoint.x + splitWidth, y: rpoint.y - splitTick},
+      {x: rpoint.x - splitWidth, y: rpoint.y - splitTick}
+    ]
+    let offas = {
+      ...Furn.prototype,
+      renderer: processing.render,
+      minWidth: 0.1
+    }
+
+    let pos = []
+    points.forEach(point => {
+      pos.push(point.x);
+      pos.push(point.y)
+    })
+    return [pos]
+  }
+
+  // 指定屏幕添加指定类型建筑或点
+  const increase = (type: string, pos: Screen, top = null, bottom = null, isDOM = false) : insertRet => {
+    if (!addEle[type]) return;
+
+    // 转化为真实点位
+    let rpoint = isDOM ? renderer.screenToRealPoint(pos) : pos
+    let obj
+    
+    if (~ATTACHCOM.indexOf(type) || ~STANDCOM.indexOf(type)) {
+      // let rpoint = renderer.screenToRealPoint(pos)
+      let ret = ~STANDCOM.indexOf(type) ? 
+        calcStandComArgs(rpoint, type):
+        calcAttachComArgs(rpoint, type);
+      if (Type.isArray(ret)) {
+        let obj1 = addEle[type](...(ret as Array<any>), top, bottom)
+
+        if (typeof obj1 === 'string') {
+          return {error: true, msg: obj1, obj: null}
+        } else {
+          obj = obj1
+        }
+      } else {
+        return ret as insertRet
+      }
+
+    } else if (~ONE.indexOf(type)){
+      obj = addEle[type](rpoint)
+
+      if (typeof obj === 'string') {
+        return {error: true, msg: obj, obj: null}
+      }
+    }
+
+    cad.preservation()
+
+    return {error: false, msg:  i18n.t('modules.model.success', { widget: ARCH[type]}), obj}
+  }
+
+  const addEle = {
+    // 添加点
+    [POINT]: (aline: WallLine, pos: Point) => {
+      processing.insertModel = true
+
+      let newPoint = processing.addPoint({
+        id: processing.getNewPointId(),
+        ...pos
+      })
+      
+      processing.data.vertex.push({
+        id: newPoint.id,
+        x: newPoint.ele.x,
+        y: newPoint.ele.y
+      })
+
+      let {lines, promis, ret} = processing.lineInsertPoint(aline, newPoint.ele)
+
+      if (ret) {
+        promis.then(() => processing.insertModel = false)
+        return lines.map(line => line.ele)
+      }
+    },
+
+
+    [DOOR](line: WallLine, points: [Point, Point], l, top, bottom) {
+      // 添加默认的top bottom
+      return processing.addDoor({
+        pos: [
+          points[0].x, points[0].y, 
+          points[1].x, points[1].y
+        ],
+        line: processing.getLineId(line),
+        start: false,
+        within: 0
+      }).ele
+    },
+
+    [SLIDEDOOR](line: WallLine, points: [Point, Point], l, top, bottom) {
+      // 添加默认的top bottom
+      return processing.addSlideDoor({
+        within: 0,
+        pos: [
+          points[0].x, points[0].y, 
+          points[1].x, points[1].y
+        ],
+        line: processing.getLineId(line),
+      }).ele
+    },
+
+    [GROUNDCASE](line: WallLine, points: [Point, Point], l, top, bottom) {
+      // 添加默认的top bottom
+
+      return processing.addGroundCase({
+        pos: [
+          points[0].x, points[0].y, 
+          points[1].x, points[1].y
+        ],
+        line: processing.getLineId(line),
+      }).ele
+    },
+
+    
+    [BAYCASE](line: WallLine, points: [Point, Point], l, top, bottom) {
+      // 添加默认的top bottom
+
+      return processing.addBayCase({
+        pos: [
+          points[0].x, points[0].y, 
+          points[1].x, points[1].y
+        ],
+        line: processing.getLineId(line),
+        within: 0
+      }).ele
+    },
+
+    [CASEMENT](line: WallLine, points: [Point, Point], l, top, bottom) {
+
+
+      return processing.addCase({
+        pos: [
+          points[0].x, points[0].y, 
+          points[1].x, points[1].y
+        ],
+        line: processing.getLineId(line)
+      }).ele
+    },
+
+    [COLUMN](line: WallLine, points: [Point, Point], rpoint: Point) {
+      let [p31, p32] = getDisVerticalLinePoints(line, points[0], DEFAULT[COLUMN].width)
+      let p3 = p32
+      // let face = room.ground.map(pid => cad.processing.points.find(({id}) => id === pid).ele)
+      // let lines = cad.processing.lines
+      //   .filter(({ele}) => line !== ele)
+      //   .map(({ele}) => ele)
+      // let p3 = null
+
+      // if ( pointInside(face, p31) &&
+      //   !lines.some(line => segmentsIntr(line, {points: [points[0], p31]})) ) {
+      //   p3 = p31
+      // } else if ( pointInside(face, p32) && 
+      //   !lines.some(line => segmentsIntr(line, {points: [points[0], p32]})) ) {
+      //   p3 = p32
+      // } else {
+      //   p3 = p31
+      // }
+
+      if (!p3) return i18n.t('modules.model.error_widget', { widget: i18n.t('modules.model.column')})
+
+      let [p41, p42] = getDisVerticalLinePoints(line, points[1], DEFAULT[COLUMN].tick);
+      let p4 = lineDis({points: [p41, p3]}) > lineDis({points: [p42, p3]}) ? p42 : p41
+
+      return processing.addColumn({
+        pos: [
+          points[0].x, points[0].y, 
+          points[1].x, points[1].y,
+          p3.x, p3.y,
+          p4.x, p4.y,
+        ],
+        line: processing.getLineId(line)
+      }).ele
+    },
+
+    [TAGGING](point: Point) {
+      return processing.addTagging({
+        pos: [point.x, point.y],
+        title: '',
+        content: '',
+        showTitle: true,
+        showContent: true
+      }).ele
+    },
+
+    [FURNCOLUMN](pos:Array<number>, room, top, bottom) {
+      return processing.addFurnColumn({pos, angle: 0}).ele
+    },
+
+    [FURNFLUE](pos:Array<number>, room, top, bottom) {
+      return processing.addFurnFlue({pos, angle: 0}).ele
+    },
+    
+    [LINE](point: Point) {
+      const width = DEFAULT[LINE].width
+      let addLine = { points: [
+        { x: point.x - width / 2, y: point.y },
+        { x: point.x + width / 2, y: point.y }
+      ] } as Line
+      let isCross = cad.processing.lines.some(({ele: line}) => segmentsIntr(line, addLine))
+      
+      if (isCross) {
+        addLine = { points: [
+          { x: point.x - width / 2, y: point.y },
+          { x: point.x + width / 2, y: point.y }
+        ] } as Line
+        isCross = cad.processing.lines.some(({ele: line}) => segmentsIntr(line, addLine))
+
+        if (isCross) return i18n.t('modules.model.error_widget', { widget: i18n.t('modules.model.line')})
+      }
+
+      let otherFurns = cad.processing.furnColumns.concat(cad.processing.furnFlues).map(p => p.ele);
+
+      
+      if (otherFurns.every(furn => {
+        return !isFaceIntersect(addLine.points, furn.points) &&
+            !isFaceContain(addLine.points, furn.points) &&
+            !isFaceContain(furn.points, addLine.points)
+        })
+      ) {
+        let lines = cad.processing.addRoom(addLine)
+        return lines[0].ele
+      } else {
+        return i18n.t('modules.model.error_widget', { widget: i18n.t('modules.model.line')})
+      }
+    },
+    [SEFTLINE](config) {
+      return cad.processing.addLine(config).ele
+    }
+  }
+
+  cad.increase = increase
+
+  let destroy = cad.destroy
+  cad.destroy = function(...args) {
+    renderer = null
+    processing = null
+    destroy.apply(this, args)
+  }
+}

+ 135 - 0
src/CAD/core/additional/label.ts

@@ -0,0 +1,135 @@
+import Gauge, {UpdateProps} from '../label/gauge'
+import Direction, {StyleProps} from '../label/direction'
+import {AttachCAD as CAD} from '../index'
+import Point from '../core/point'
+import WallLine from '../core/wallline'
+
+
+export interface label{
+  showGauge: () => void 
+  hideGauge: () => void 
+  showDire: () => void
+  hideDire: () => void
+  setAngle: (angle: number) => void
+  setGaugeAttrs: (args: UpdateProps) => void
+  setDireAttrs: (args: StyleProps) => void
+  setGaugeModel: (type: number) => Promise<void>
+  gauge?: Gauge
+  direction: Direction
+}
+
+// 附加显示隐藏比例尺的api,添加当前位置的label
+export const attachGauge = (cad: CAD) => {
+  let gauge: Gauge
+
+  let walls = cad.processing.lines
+  let wallPush = walls.push
+  let points = cad.processing.points
+  let pointPush = points.push
+  // 拦截点修改
+  const attchPoint = (point: Point) => {
+    point.__label_intercept = point.intercept
+    point.intercept = (...args) => {
+      gauge.update()
+      return point.__label_intercept.apply(point, args)
+    }
+  }
+  const attchLine = (line: WallLine) => {
+    line.__label_intercept = line.drag
+    line.drag = (...args) => {
+      gauge.update()
+      return line.__label_intercept.apply(line, args)
+    }
+  }
+
+  cad.setGaugeAttrs = (args) => {
+    gauge.__state.direct = true
+    Object.keys(args).forEach(key => {
+      gauge[key] = args[key]
+    })
+    gauge.__state.direct = false
+    gauge.update()
+  }
+
+  // 显示比例尺
+  cad.showGauge = () => {
+    if (!gauge) {
+      gauge = new Gauge({ cad })
+      cad.gauge = gauge
+    }
+    // 拦截添加
+    walls.push = (...args) => {
+      let ret = wallPush.apply(walls, args)
+      gauge.update()
+      args.forEach(({ele}) => attchLine(ele))
+      return ret
+    }
+    
+    points.forEach(({ele}) => attchPoint(ele))
+    walls.forEach(({ele}) => attchLine(ele))
+    points.push = (...args) => {
+      args.forEach(({ele}) => attchPoint(ele))
+      return pointPush.apply(points, args)
+    }
+
+    cad.processing.render.push(gauge)
+  }
+
+  cad.setGaugeModel = (type) => {
+    if (gauge) {
+      gauge.surround = type === 1
+      return new Promise(r => gauge.nextTick(r))
+    } else {
+      return new Promise(r => r())
+    }
+  }
+
+  // 隐藏比例尺
+  cad.hideGauge = () => {
+    walls.push = wallPush
+    points.push = pointPush
+    points.forEach(({ele}) => ele.intercept = ele.__label_intercept)
+
+    cad.processing.render.remove(gauge)
+  }
+
+  let destroy = cad.destroy
+  cad.destroy = function(...args) {
+    cad.hideGauge()
+    walls = null
+    wallPush = null
+    points = null
+    pointPush = null
+    destroy.apply(this, args)
+  }
+
+
+  let direction = new Direction({angle: 0, renderer: cad.processing.render})
+  
+  cad.showDire = () => {
+    direction.show = true
+    cad.processing.render.push(direction)
+  }
+
+  cad.hideDire = () => {
+    direction.show = false
+    cad.processing.render.remove(direction)
+  }
+
+  cad.setDireAttrs = (args) => {
+    Object.keys(args).forEach(key => {
+      direction[key] = args[key]
+    })
+    return new Promise(r => direction.nextTick(r))
+  }
+
+  cad.setAngle = (angle) => {
+    cad.processing.data.dire = angle
+    direction.angle = angle
+    direction.nextTick(() => {
+      cad.preservation()
+    })
+  }
+
+  cad.direction = direction
+}

+ 95 - 0
src/CAD/core/additional/rote.ts

@@ -0,0 +1,95 @@
+import Rote, {Point} from '../label/route'
+import {AttachCAD as CAD} from '../index'
+
+
+
+export interface Local{
+  pushRoute: (point: Point) => void,
+  activeRoute: (index: number) => void,
+  popRoute: (index: number) => void,
+  clearRoutes: (cb?: Function) => void,
+  setRoutes: (points: Array<Point>) => void,
+  showRotes: () => void,
+  hideRotes: () => void,
+}
+
+// 附加走过路径的label
+export const attachRote = (cad: CAD) => {
+  let rote = new Rote({renderer: cad.processing.render, local: []})
+  rote.zIndex = 2
+  cad.processing.render.push(rote)
+  
+  rote.setActive = active => {
+    cad.activeRoute(rote.local.indexOf(active))
+  }
+
+  cad.showRotes = () => {
+    rote.show = true
+    return new Promise(r => rote.nextTick(r))
+  }
+
+  cad.hideRotes = () => {
+    rote.show = false
+    return new Promise(r => rote.nextTick(r))
+  }
+
+  cad.setRoutes = points => {
+    const queryTemp = (i) => {
+      let point = points.slice(0, i).reverse().find(point => !point.rangn)
+      if (point) {
+        return point
+      } else {
+        return points.slice(i).find(point => !point.rangn)
+      }
+    }
+
+    points = points.map((point, i) => {
+      if ((point as any).rangn) {
+        let point = queryTemp(i)
+        if (point) {
+          return {...point, rangn: true}
+        }
+      }
+      return { ...point }
+    })
+
+    cad.clearRoutes(() => {
+      rote.local = [...points]
+    })
+  }
+  
+  // 添加当前点位
+  cad.pushRoute = point => {
+    cad.clearRoutes(() => {
+      setTimeout(() => {
+        // cad.setRoutes([...rote.local, point])
+      }, 100)
+    })
+  }
+  // 当前正在哪个点位
+  cad.activeRoute = index => {
+    rote.nextTick(() => {
+      rote.active = rote.local[index]
+    })
+  }
+
+  // 删除点位
+  cad.popRoute = index => {
+    rote.local.splice(index, 1)
+    rote.nextTick(() => rote.local = [...rote.local])
+  }
+  // 清除所有点位
+  cad.clearRoutes = (cb) => {
+    rote.local = []
+    rote.dialog = 0
+    rote.nextTick(() => cb && cb())
+  }
+
+  
+  let destroy = cad.destroy
+  cad.destroy = function(...args) {
+    cad.clearRoutes()
+    rote = null
+    destroy.apply(this, args)
+  }
+}

+ 91 - 0
src/CAD/core/additional/screenshot.ts

@@ -0,0 +1,91 @@
+import {AttachCAD as CAD} from '../index'
+import {convertBase64UrlToBlob} from '../util'
+import processing, { Data } from '../base/processing/index'
+
+export interface screenArgs {
+  width: number, 
+  height: number, 
+  bgColor: string, 
+  data: Data, 
+  padding: number,
+  showGauge: boolean,
+  lineWidth: number
+}
+export interface Screenshot {
+  // 截图,传入要截图的宽高返回promise,并返回留白区域
+  screenshot: (screenArgs) => 
+    Promise<{file: Blob, bound: {top: number, right: number, left: number, bottom: number}, width: number, height: number}>
+}
+
+// 附加截图功能
+export const attachScreenshot = (cad: CAD) => {
+  let render = cad.processing.render
+
+  cad.screenshot = async ({width = 2048, height = 2048, bgColor = 'rgba(0,0,0,0)', data, spadding}) => {
+    let props = {...render.props}
+    let image = new Image()
+    let canvas = document.createElement('canvas')
+    let context = canvas.getContext('2d')
+    let xs = cad.processing.points.map(({ele: {x}}) => x)
+    let ys = cad.processing.points.map(({ele: {y}}) => y)
+
+    // 适应当前宽高
+    let padding = cad.padding
+    cad.padding = spadding
+    cad.adapt(data, width, height)
+
+    // await cad.hideSign()
+
+    // 涂绘canvas清除加背景
+    canvas.width = width
+    canvas.height = height
+    context.fillStyle = bgColor
+    context.fillRect(0, 0, canvas.width, canvas.height)
+    return new Promise(async (resolve) => {
+      // cad.hideGauge && cad.hideGauge()
+      image.onload = async () => {
+        // img绘制到canvas
+        context.drawImage(image, 0, 0)
+        // const data = {
+        //   width,
+        //   height,
+        //   file: convertBase64UrlToBlob(canvas.toDataURL('image/png')),
+        //   left: cad.processing.render.realPointToScreen({x: Math.min(...xs), y: 0}).x,
+        //   top: cad.processing.render.realPointToScreen({x: 0, y: Math.min(...ys)}).y,
+        //   bottom: height - cad.processing.render.realPointToScreen({x: 0, y: Math.max(...ys)}).y,
+        //   right: width - cad.processing.render.realPointToScreen({x: Math.max(...xs), y: 0}).x
+        // }
+        
+        const data = {
+          file: convertBase64UrlToBlob(canvas.toDataURL('image/png')),
+          width,
+          height,
+          left: cad.processing.render.realPointToScreen({x: Math.min(...xs), y: 0}).x,
+          top: cad.processing.render.realPointToScreen({x: 0, y: Math.min(...ys)}).y,
+          bottom: height - cad.processing.render.realPointToScreen({x: 0, y: Math.max(...ys)}).y,
+          right: width - cad.processing.render.realPointToScreen({x: Math.max(...xs), y: 0}).x,
+          bound: {
+            left: Math.min(...xs),
+            top: Math.min(...ys),
+            bottom: Math.max(...ys),
+            right: Math.max(...xs),
+          }
+        }
+        render.props = props
+        cad.padding = padding
+        render.adaptLayer()
+        // await cad.showSign()
+        resolve(data)
+      }
+      // 将svg载入img
+      image.src = 'data:image/svg+xml;base64,' + window.btoa(unescape(encodeURIComponent(render.svg.outerHTML)))
+    })
+  }
+
+
+  let destroy = cad.destroy
+  cad.destroy = function(...args) {
+    render = null
+    destroy.apply(this, args)
+  }
+}

+ 67 - 0
src/CAD/core/additional/sign.ts

@@ -0,0 +1,67 @@
+import Sign, {StyleProps as SignProps} from '../label/sign'
+import {AttachCAD as CAD} from '../index'
+import {Point as geoPoint} from '../geometry'
+import {type} from '../util'
+
+
+export interface sign{
+  setSign: (point: geoPoint, dire?: number) => void,
+  hideSign: () => Promise<void>,
+  showSign: () => Promise<void>,
+  setDefaultSignStyle: (args: SignProps) => void
+}
+
+// 附加显示隐藏比例尺的api,添加当前位置的label
+export const attachSign = (cad: CAD) => {
+  let signStyle = {
+    border: 2,
+    r: 5
+  }
+  Sign.Setting.set(cad.processing.render, signStyle)
+
+  let sign = new Sign({
+    pos: {x: -2, y: 5},
+    dire: 0,
+    renderer: cad.processing.render
+  })
+  sign.zIndex = 1
+  cad.processing.render.push(sign)
+  
+  // 添加设置当前点位方向
+  cad.setSign = (point, dire) => {
+    if (type.isNumber(dire)) sign.dire = dire
+    sign.pos = point
+  }
+
+  cad.hideSign = () => {
+    console.log('-------')
+    sign.show = false
+    return new Promise(r => sign.nextTick(r))
+  }
+
+  cad.showSign = () => {
+    sign.show = true
+    return new Promise(r => sign.nextTick(r))
+  }
+  
+  cad.setDefaultSignStyle = args => {
+    if (args.border) {
+      signStyle.border = args.border
+      sign.border = args.border
+    }
+    if (args.r) {
+      signStyle.r = args.r
+      sign.r = args.r
+    }
+    if (args.color) {
+      sign.color = args.color
+    }
+  }
+
+  let destroy = cad.destroy
+  cad.destroy = function(...args) {
+    sign = null
+    destroy.apply(this, args)
+  }
+
+}

+ 64 - 0
src/CAD/core/additional/styleSet.ts

@@ -0,0 +1,64 @@
+
+import Point from '../core/point'
+import FixedPoint from '../core/fixedpoint'
+import {AttachCAD as CAD} from '../index'
+import Line from '../core/fixedline'
+
+interface pointStyle {
+  storkeColor?: string,
+  fillColor?: string,
+  hoverStorkeColor?: string,
+  hoverFillColor?: string
+}
+
+interface lineStyle {
+  width: number,
+  color: string
+}
+
+export interface Style {
+  setDefaultPointStyle: (args: pointStyle) => void,
+  setDefaultLineStyle: (args: lineStyle) => void,
+}
+
+// 给cad点设置默认样式
+export const attachStyle = (cad: CAD) => {
+  let fixedPointStyle = {
+    fillColor: 'rgb(0, 200, 175)',
+    storkeColor: 'green'
+  }
+  let pointStyle = {
+    fillColor: 'rgba(245, 255, 0, 0.7)',
+    storkeColor: 'rgba(245, 255, 255, 0.3)'
+  }
+  FixedPoint.Setting.set(cad.processing.render, fixedPointStyle)
+  Point.Setting.set(cad.processing.render, pointStyle)
+
+  cad.setDefaultPointStyle = (args) => {
+    args.storkeColor && (fixedPointStyle.storkeColor = args.storkeColor)
+    args.fillColor && (fixedPointStyle.fillColor = args.fillColor)
+    args.hoverStorkeColor && (pointStyle.storkeColor = args.hoverStorkeColor)
+    args.hoverFillColor && (pointStyle.fillColor = args.hoverFillColor)
+
+    setTimeout(() => {
+      cad.loadData(cad.getData());
+    }, 100)
+  }
+
+
+
+  let lineStyle = {
+    width: 3,
+    color: 'rgb(255,255,255)'
+  }
+  Line.Setting.set(cad.processing.render, lineStyle)
+
+  cad.setDefaultLineStyle = args => {
+    args.width && (lineStyle.width = args.width)
+    args.color && (lineStyle.color = args.color)
+    
+    setTimeout(() => {
+      cad.loadData(cad.getData());
+    }, 100)
+  }
+}

+ 75 - 0
src/CAD/core/additional/transform.ts

@@ -0,0 +1,75 @@
+import {AttachCAD} from '../index'
+
+declare global { interface Window { opera: boolean; } }
+export interface DOMTransform {
+  openMouseHandle: () => void
+  propsChange: () => void
+  closeMouseHandle: () => void
+}
+
+// 给cad附加矩阵转换功能 如放大缩小
+export const attchDOMTranform = (cad: AttachCAD) => {
+  let render = cad.processing.render
+
+
+  // 点击在cad上认为要平移
+  const down = (dev: MouseEvent) => {
+    if (dev.target !== render.svg) return;
+    const left = render.props.left
+    const top = render.props.top
+    const move = (mev: MouseEvent) => {
+      render.props.left = left - (dev.pageX - mev.pageX) * render.props.multiple
+      render.props.top = top - (dev.pageY - mev.pageY) * render.props.multiple
+      render.adaptLayer()
+      cad.propsChange && cad.propsChange()
+
+      mev.preventDefault()
+    }
+    
+    const up = () => {
+      document.documentElement.removeEventListener('mousemove', move, false)
+      document.documentElement.removeEventListener('mouseup', up, false)
+    }
+
+    document.documentElement.addEventListener('mousemove', move, false)
+    document.documentElement.addEventListener('mouseup', up, false)
+
+    dev.preventDefault()
+  }
+
+
+  // 缩放
+  const wheel = (ev: any) => {
+    let delta = ev.wheelDelta ? //IE、chrome浏览器使用的是wheelDelta,并且值为“正负120”
+      (window.opera ? -ev.wheelDelta / 120 : ev.wheelDelta / 120) : //因为IE、chrome等向下滚动是负值,FF是正值,为了处理一致性,在此取反处理
+      (ev.detail ?  -ev.detail / 3 : 0); //FF浏览器使用的是detail,其值为“正负3”
+
+    let scale = render.props.scale + (delta > 0 ? 0.08 : -0.08)
+    if (scale > 0.1) {
+      render.props.scale = scale
+      render.adaptLayer()
+      cad.propsChange && cad.propsChange()
+    }
+  }
+
+
+  // 开启缩放模式
+  cad.openMouseHandle = () => {
+    render.layer.addEventListener('mousedown', down, false)
+    render.layer.addEventListener('mousewheel', wheel, false)
+  }
+
+  // 关闭缩放模式
+  cad.closeMouseHandle = () => {
+    render.layer.removeEventListener('mousedown', down, false)
+    render.layer.removeEventListener('mousewheel', wheel, false)
+  }
+
+  
+  let destroy = cad.destroy
+  cad.destroy = function(...args) {
+    cad.closeMouseHandle()
+    render = null
+    destroy.apply(this, args)
+  }
+}

+ 186 - 0
src/CAD/core/architecture/bayCase.ts

@@ -0,0 +1,186 @@
+import {SVGURI} from '../constant/Element'
+import {point} from '../core/fixedpoint'
+import LineArch, {LineArchProps} from './linearch'
+import {ElementProps} from '../core/element'
+import {lineDis, getLineDisSelectPoint, getDisVerticalLinePoints, pointInside, getDisPointLinePoints, Point} from '../geometry'
+import Line from '../core/wallline'
+
+export interface CaseProps extends PublicProps {
+  points: [point, point],
+}
+
+export interface PublicProps extends ElementProps {
+  top: number,
+  bottom: number,
+  attachment: Line,
+  within?: number,
+  width?: number
+}
+interface ArchProps extends LineArchProps,PublicProps {
+  fill?: string
+  stroke?: string
+}
+
+
+/**
+ * @category core
+ * @subcategory CAD_Architecture
+ */
+class BayCase extends LineArch<ArchProps> {
+  out: SVGPathElement
+  in: SVGPathElement
+  left: SVGRectElement
+  right: SVGRectElement
+  ctl: [point, point]
+
+  /**
+   * 构造函数
+   * @param {CaseProps} 
+   */
+  constructor({points, within = 0, width = 1, ...args}: CaseProps) {
+    points[0].fillColor = 'rgba(0,0,0,0)'
+    points[1].fillColor = 'rgba(0,0,0,0)'
+    super({linePoints: points, fill: 'rgba(243, 255, 0, 0)', stroke: 'rgba(255,255,255,0.5)', within, width, ...args})
+  }
+
+  grentNode () {
+    let node = document.createElementNS(SVGURI, 'g')
+
+    node.innerHTML = `
+      <path class="ground-case-out" stroke="${this.stroke}" fill="rgba(0,0,0,0)"/>
+      <path class="ground-case-in" stroke="${this.stroke}" fill="rgba(0,0,0,0)" />
+      <rect width="0.00001" height="0.00001" class="ground-case-left" stroke="rgba(255,255,255,0)" />
+      <rect width="0.00001" height="0.00001" class="ground-case-right" stroke="rgba(255,255,255,0)" />
+    `
+    
+    this.out = node.querySelector('.ground-case-out')
+    this.in = node.querySelector('.ground-case-in')
+    this.left = node.querySelector('.ground-case-left')
+    this.right = node.querySelector('.ground-case-right')
+
+    this.nextTick(() => {
+      node.appendChild(this.linePoints[0].real)
+      node.appendChild(this.linePoints[1].real)
+    })
+    return node
+  }
+
+  setHoverStyle() {
+    this.fill = 'rgba(243, 255, 0, 0.8)'
+  }
+
+  setUnHoverStyle() {
+    this.fill = 'rgba(0,0,0,0)'
+  }
+
+  getOutInPath(point1, point2, width, oiwidth, height, jstart: any = 0, jend: any = 0) {
+    let half = width
+    let starts = getDisVerticalLinePoints(this.attachment, point1, half)
+    let ends = getDisVerticalLinePoints(this.attachment, point2, half)
+    
+    let start = this.within ? starts[0] : starts [1]
+    let end
+
+
+    if (start) {
+      end = ends[starts.indexOf(start)]
+    } else {
+      end = this.within ?
+        ends.find(point => this.rooms.find(room => !pointInside(room, point))) :
+        ends.find(point => this.rooms.find(room => pointInside(room, point)));
+
+      if (end) {
+        start = starts[ends.indexOf(end)]
+      } else {
+        start = starts[0]
+        end = ends[0]
+      }
+    }
+
+    jstart = jstart || start
+    jend = jend || end
+
+
+    let startHs = getDisVerticalLinePoints(this.attachment, point1, height)
+    
+    let startH = startHs.sort((a, b) => lineDis({points: [jstart, a]}) - lineDis({points: [jstart, b]}))[1]
+    let endHs = getDisVerticalLinePoints(this.attachment, point2, height)
+    let endH = endHs.sort((a, b) => lineDis({points: [jend, a]}) - lineDis({points: [jend, b]}))[1]
+    
+    return [start, end, endH, startH, start]
+  }
+
+  update() {
+    let width = this.width * 3 * this.multiple
+    let oiwidth = this.width * this.multiple
+    let outHeight = lineDis({points: this.linePoints}) * 0.4
+
+    if (outHeight / this.multiple > 30) {
+      outHeight = 30 * this.multiple
+    }
+
+    let point1 = this.linePoints[0] as Point
+    let point2 = this.linePoints[1] as Point
+    let [p1, p2] = getDisPointLinePoints(this.attachment, point1, width)
+    let [p3, p4] = getDisPointLinePoints(this.attachment, point2, width)
+    point1 = lineDis({points: [p1, point2]}) > lineDis({points: [p2, point2]}) ? p1 : p2
+    point2 = lineDis({points: [p3, point1]}) > lineDis({points: [p4, point1]}) ? p3 : p4
+
+    if (isNaN(p1.x)) return;
+    
+    this.left.setAttribute('stroke-width', width.toString())
+    this.left.setAttribute('x', this.linePoints[0].x.toString())
+    this.left.setAttribute('y', this.linePoints[0].y.toString())
+    this.right.setAttribute('stroke-width', width.toString())
+    this.right.setAttribute('x', this.linePoints[1].x.toString())
+    this.right.setAttribute('y', this.linePoints[1].y.toString())
+
+    let points = this.getOutInPath(point1, point2, width, oiwidth, outHeight)
+    this.ctl = [points[2], points[3]]
+
+
+    this.out.setAttribute('fill', this.fill)
+    this.out.setAttribute('stroke-width', oiwidth.toString())
+    this.out.setAttribute('stroke', this.stroke)
+    this.out.setAttribute(
+      'd', 
+      points.map((point, i) => (!i ? 'M ' : 'L ') + `${point.x} ${point.y}`).join(' ')
+    )
+    
+    
+    let start = getLineDisSelectPoint({points: this.linePoints}, point1, width)
+    let end = getLineDisSelectPoint({points: this.linePoints}, point2, width)
+
+    
+    this.in.setAttribute('fill', this.fill)
+    this.in.setAttribute('stroke-width', oiwidth.toString())
+    this.in.setAttribute('stroke', this.stroke)
+    this.in.setAttribute(
+      'd', 
+      this.getOutInPath(start, end, width, oiwidth, outHeight - lineDis({points: [start, point1]}), points[0], points[1])
+        .map((point, i) => (!i ? 'M ' : 'L ') + `${point.x} ${point.y}`).join(' ')
+    )
+    
+    this.attachment.update()
+  }
+  
+  dragStart(ev) {
+    this.move = this.linePoints[0].real === ev.target || this.linePoints[1].real === ev.target
+    this.dragStartPoints = this.linePoints.map(point => ({x: point.x, y: point.y}))
+  }
+
+  drag(offset) {
+    if (this.move) return;
+    this.linePoints.forEach((point, i) => {
+      point.x = this.dragStartPoints[i].x + offset.x * this.multiple
+      point.y = this.dragStartPoints[i].y + offset.y * this.multiple
+    })
+  }
+
+  dragEnd() {
+    this.move = false
+    delete this.dragStartPoints
+  }
+}
+
+export default BayCase

+ 157 - 0
src/CAD/core/architecture/casement.ts

@@ -0,0 +1,157 @@
+import Line from '../core/line'
+import WallLine from '../core/wallline'
+import {SVGURI} from '../constant/Element'
+import {point} from '../core/fixedpoint'
+import LineArch, {LineArchProps} from './linearch'
+import LinePoint from '../core/linepoint'
+import {ElementProps} from '../core/element'
+import { getDisVerticalLinePoints } from '../geometry'
+
+export interface CastStyle {
+  fill?: string,
+  seam?: string,
+  width?: number,
+  stamWidth?: number
+}
+
+interface PublicProps extends CastStyle { hover?: CastStyle, attachment: WallLine }
+interface ArchProps extends LineArchProps,PublicProps {}
+export interface CaseProps extends PublicProps, ElementProps {
+  points: [point, point],
+  top: number,
+  bottom: number
+}
+
+/**
+ * @category core
+ * @subcategory CAD_Architecture
+ */
+class Casement extends LineArch<ArchProps> {
+  points: [LinePoint, LinePoint]
+  init: CastStyle
+  outLine: Line
+  innerline: Line
+  eleHover: boolean
+  clip1: SVGPathElement
+  clip2: SVGPathElement
+  clip3: SVGPathElement
+  clip4: SVGPathElement
+
+  constructor({points, fill = 'rgba(0,0,0,0)', seam = 'rgba(255,255,255,0)', width = 6, stamWidth = 0.5, hover, ...args}: CaseProps) {
+    let init = {fill, seam, width, stamWidth}
+    hover = hover || {...init, fill: 'rgba(243, 255, 0, 0.8)'}
+    super({linePoints: points, hover, ...args, ...init})
+    this.eleHover = false
+  }
+
+  grentNode () {
+    let node = document.createElementNS(SVGURI, 'g')
+
+    this.nextTick(() => {
+      this.outLine = new Line({ points: this.linePoints, width: this.width, color: this.fill, renderer: this.renderer })
+      this.innerline = new Line({ points: this.linePoints, width: this.stamWidth, color: this.seam, renderer: this.renderer })
+      this.outLine.click = this.innerline.click = () => this.changeSelect(true)
+      node.appendChild(this.outLine.real)
+      node.appendChild(this.innerline.real)
+      node.appendChild(this.linePoints[0].real)
+      node.appendChild(this.linePoints[1].real)
+
+      this.interceptUpdateStyle()
+      this.outLine.update()
+      this.innerline.update()
+    })
+
+    this.clip1 = document.createElementNS(SVGURI, 'path')
+    this.clip2 = document.createElementNS(SVGURI, 'path')
+    this.clip3 = document.createElementNS(SVGURI, 'path')
+    this.clip4 = document.createElementNS(SVGURI, 'path')
+    this.clip1.setAttribute('stroke', 'rgba(255,255,255,0.5)')
+    this.clip2.setAttribute('stroke', 'rgba(255,255,255,0.5)')
+    this.clip3.setAttribute('stroke', 'rgba(255,255,255,0.5)')
+    this.clip4.setAttribute('stroke', 'rgba(255,255,255,0.5)')
+
+    node.appendChild(this.clip1)
+    node.appendChild(this.clip2)
+    node.appendChild(this.clip3)
+    node.appendChild(this.clip4)
+
+    return node
+  }
+
+  getStyle() : CastStyle {
+    return (this.eleHover || this.select) ? 
+      this.hover: 
+      {
+        width: this.width,
+        fill: this.fill,
+        seam: this.seam,
+        stamWidth: this.stamWidth
+      }
+  }
+
+  // 拦截修改并添加是否激活的样式
+  interceptUpdateStyle() {
+    let outIntercept = this.outLine.intercept
+    let innerIntercept = this.innerline.intercept
+    
+    this.outLine.intercept = ([seft], {width, color}) => {
+      if (seft === this.outLine && (width || color)) {
+        let style = this.getStyle()
+        return {width: style.width, color: style.fill}
+      } else {
+        return outIntercept.call(this)
+      }
+    }
+    
+    this.innerline.intercept = ([seft], {width, color}) => {
+      if (seft === this.innerline && (width || color)) {
+        let style = this.getStyle()
+        return {width: style.stamWidth, color: style.seam}
+      } else {
+        return innerIntercept.call(this)
+      }
+    }
+  }
+
+  update() {
+    if (this.outLine) {
+      this.outLine.width = Date.now()
+      this.innerline.width = Date.now()
+    }
+    
+    let [p1, p3] = getDisVerticalLinePoints(this.attachment, this.linePoints[0], this.width * 0.4 * this.multiple)
+    let [p2, p4] = getDisVerticalLinePoints(this.attachment, this.linePoints[1], this.width * 0.4 * this.multiple)
+    let [p5, p7] = getDisVerticalLinePoints(this.attachment, this.linePoints[0], this.width * 0.1 * this.multiple)
+    let [p6, p8] = getDisVerticalLinePoints(this.attachment, this.linePoints[1], this.width * 0.1 * this.multiple)
+
+    if (isNaN(p1.x)) return;
+
+    this.clip1.setAttribute('stroke-width', (this.multiple).toString())
+    this.clip1.setAttribute('d', `M ${p1.x} ${p1.y} L ${p2.x} ${p2.y}`)
+    this.clip2.setAttribute('stroke-width', (this.multiple).toString())
+    this.clip2.setAttribute('d', `M ${p3.x} ${p3.y} L ${p4.x} ${p4.y}`)
+    this.clip3.setAttribute('stroke-width', (this.multiple).toString())
+    this.clip3.setAttribute('d', `M ${p5.x} ${p5.y} L ${p6.x} ${p6.y}`)
+    this.clip4.setAttribute('stroke-width', (this.multiple).toString())
+    this.clip4.setAttribute('d', `M ${p7.x} ${p7.y} L ${p8.x} ${p8.y}`)
+    this.attachment.update()
+  }
+
+  wallSelect() {
+    this.update && this.update()
+  }
+  
+  enter() {
+    this.eleHover = true
+    this.update()
+  }
+
+  leave() {
+    this.eleHover = false
+    this.update()
+  }
+
+  drag(offset){}
+}
+
+export default Casement

+ 412 - 0
src/CAD/core/architecture/column.ts

@@ -0,0 +1,412 @@
+import Line from '../core/wallline'
+import {SVGURI} from '../constant/Element'
+import {point} from '../core/fixedpoint'
+import Point from '../core/point'
+import { CADElement, ElementProps } from '../core/element'
+import LineArch, {LineArchProps} from './linearch'
+import { type } from '../util'
+import LinePoint from '../core/linepoint'
+import {
+  Point as geoPoint,
+  Line as geoLine,
+  lineDis,
+  getLinePoint,
+  segmentsIntr,
+  getDisVerticalLinePoints,
+  getDisPointLinePoints,
+  lineVector
+} from '../geometry'
+
+export interface ColumnStyle {
+  fill?: string,
+  stroke?: string,
+  width?: number
+}
+
+export interface HoverColumnStyle extends ColumnStyle { strokeBorder: string }
+
+interface PublicProps extends ColumnStyle { hover?: HoverColumnStyle, attachment: Line }
+interface ArchProps extends LineArchProps,PublicProps {
+  otherPoints: Array<Point>
+}
+export interface ColumnProps extends PublicProps, ElementProps { points: [point, point, point, point] }
+// 判断是否在当前DOM上
+const is = (state, obj) => state.data === obj || (state.trigger && is(state.trigger, obj))
+
+/**
+ * @category core
+ * @subcategory CAD_Architecture
+ */
+class Column extends LineArch<ArchProps> {
+  selectLineIndex: number
+  linePoints: [LinePoint, LinePoint]
+  leftLine: SVGPathElement
+  topLine: SVGPathElement
+  bottomLeft: SVGPathElement
+  rightLine: SVGPathElement
+  overLine1: SVGPathElement
+  overLine2: SVGPathElement
+  wrapPath: SVGPathElement
+  lines: Array<SVGPathElement>
+  rel: SVGGElement
+  points: Array<Point>
+  init: ColumnStyle
+  readyPoints: Array<point>
+  private firstLinePoints: [geoPoint, geoPoint]
+  private hoverIndex: number
+  thickness: number
+  
+
+  constructor({points, fill = 'rgba(0,0,0,0)', stroke = 'rgba(255,255,255,0.5)', width = 1, hover, ...args}: ColumnProps) {
+    let linePoints: [point, point] = [points[0], points[1]]
+    let otherPoints = points.slice(2).map(p => new Point({...p, renderer: args.renderer}))
+    let init = {fill, stroke, width}
+    hover = hover || {...init, fill: 'rgba(243, 255, 0, 0.8)', strokeBorder: 'rgba(243, 255, 0, 0.8)'}
+    super({linePoints, hover, otherPoints, ...init, ...args})
+    this.points = this.linePoints.concat(this.otherPoints as any)
+    this.selectLineIndex = -1
+    this.init = init
+    this.cacheVers = [
+      lineVector({points: [this.points[1], this.points[2]]}),
+      lineVector({points: [this.points[0], this.points[3]]})
+    ]
+
+    this.__enterHandle = () => {
+      if (this.hasTriggerEvent()) return;
+      if (!~this.selectLineIndex || this.select) {
+        Object.keys(this.hover).forEach(k => this[k] = this.hover[k])
+      }
+    }
+    this.__leaveHandle = () => {
+      if (this.hasTriggerEvent()) return;
+      if (!this.select) {
+        Object.keys(this.init).forEach(k => this[k] = this.init[k])
+      }
+    }
+    this.__lineEnter = (ev) => {
+      if (this.hasTriggerEvent()) return;
+      if (!~this.selectLineIndex && !this.__draging) {
+        this.selectLineIndex = this.lines.indexOf(ev.target)
+        this.update()
+      }
+    }
+    this.__lineLeave = (ev) => {
+      if (this.hasTriggerEvent()) return;
+      if (!this.__draging) {
+        this.selectLineIndex = -1
+        this.update()
+      }
+    }
+    this.__lineClick = ev => ev.stopPropagation()
+  }
+
+  updateFirstLinePoints() {
+    this.firstLinePoints = this.attachment.points.map(p => ({x: p.x, y: p.y})) as [geoPoint, geoPoint]
+  }
+
+  grentNode () {
+    let node = document.createElementNS(SVGURI, 'g')
+
+    this.wrapPath = document.createElementNS(SVGURI, 'path')
+    this.leftLine = document.createElementNS(SVGURI, 'path')
+    this.rightLine = document.createElementNS(SVGURI, 'path')
+    this.bottomLeft = document.createElementNS(SVGURI, 'path')
+    this.topLine = document.createElementNS(SVGURI, 'path')
+    this.overLine1 = document.createElementNS(SVGURI, 'path')
+    this.overLine2 = document.createElementNS(SVGURI, 'path')
+    this.lines = [this.leftLine, this.rightLine, this.topLine, this.overLine1, this.overLine2, this.bottomLeft]
+
+    this.wrapPath.setAttribute('stroke', 'rgba(0,0,0,0)')
+    this.nextTick(() => {
+      this.points = this.linePoints.concat(this.otherPoints as any)
+      this.addEvent()
+      this.updateThickness()
+      this.updateFirstLinePoints()
+    })
+    node.appendChild(this.overLine1)
+    node.appendChild(this.overLine2)
+    node.appendChild(this.wrapPath)
+    node.appendChild(this.leftLine)
+    node.appendChild(this.topLine)
+    node.appendChild(this.rightLine)
+    node.appendChild(this.bottomLeft)
+
+    return node
+  }
+
+  update() {
+    if (!this.points) return;
+
+    let width = this.width * this.multiple
+    
+    this.lines.forEach(line => {
+      line.setAttribute('stroke-width', width.toString())
+      line.setAttribute('stroke-linecap', 'round')
+      line.setAttribute('stroke', this.stroke)
+    })
+
+    if (type.isNumber(this.hoverIndex)) {
+      this.lines[this.hoverIndex].setAttribute('stroke', this.hover.strokeBorder)
+    } else {
+      this.wrapPath.setAttribute('stroke-width', '0')
+      this.wrapPath.setAttribute('fill', this.fill)
+      this.wrapPath.setAttribute('d', `M ${this.points.map(({x, y}) => x + ' ' + y).join(' L ')}`)
+    }
+    if (isNaN(this.points[0].x)) return;
+
+    this.overLine1.setAttribute('d', `M ${this.points[0].x} ${this.points[0].y} L ${this.points[2].x} ${this.points[2].y}`)
+    this.overLine2.setAttribute('d', `M ${this.points[3].x} ${this.points[3].y} L ${this.points[1].x} ${this.points[1].y}`)
+    this.topLine.setAttribute('d', `M ${this.points[3].x} ${this.points[3].y} L ${this.points[2].x} ${this.points[2].y}`)
+    this.leftLine.setAttribute('d', `M ${this.points[0].x} ${this.points[0].y} L ${this.points[3].x} ${this.points[3].y}`)
+    this.rightLine.setAttribute('d', `M ${this.points[2].x} ${this.points[2].y} L ${this.points[1].x} ${this.points[1].y}`)
+    this.bottomLeft.setAttribute('d', `M ${this.points[0].x} ${this.points[0].y} L ${this.points[1].x} ${this.points[1].y}`)
+
+    if (~this.selectLineIndex) {
+      this.lines[this.selectLineIndex].setAttribute('stroke', this.hover.strokeBorder)
+    }
+    this.attachment.update()
+  }
+
+  hasTriggerEvent() : boolean {
+    let ret = CADElement.examples.get(this.renderer).some(e => e.__draging)
+    return ret
+  }
+
+  wallSelect() {
+    if (this.select) {
+      Object.keys(this.hover).forEach(k => this[k] = this.hover[k])
+    } else {
+      Object.keys(this.init).forEach(k => this[k] = this.init[k])
+    }
+  }
+
+  addEvent() {
+
+    this.wrapPath.addEventListener('mouseenter', this.__enterHandle, false)
+    this.wrapPath.addEventListener('mouseleave', this.__leaveHandle, false)
+    this.lines.forEach(line => {
+      line.addEventListener('mouseenter', this.__lineEnter, false)
+      line.addEventListener('mouseleave', this.__lineLeave, false)
+      line.addEventListener('click', this.__lineClick, false)
+    })
+  }
+
+  // 更新当前柱子的厚度
+  updateThickness(line: [point, point] = [this.points[0], this.points[3]]) {
+    this.thickness = lineDis({points: line})
+  }
+
+  // 检查外围点位置是否合格
+  checkPeripheral(points: Array<point> = this.points) {
+    return true
+    this.readyPoints = points
+    return this.attachment.wallLines.every(line => 
+      line === this.attachment || (
+        !segmentsIntr(line, {points: [points[0], points[3]]}) &&
+        !segmentsIntr(line, {points: [points[1], points[2]]})
+      )
+    )
+  }
+
+  // 获取外围点位置
+  getPeripheral(points: Array<point> = this.points) {
+    let chang1 = {
+      x: this.firstLinePoints[0].x - this.attachment.points[0].x,
+      y: this.firstLinePoints[0].y - this.attachment.points[0].y
+    }
+    let chang2 = {
+      x: this.firstLinePoints[1].x - this.attachment.points[1].x,
+      y: this.firstLinePoints[1].y - this.attachment.points[1].y
+    }
+    let chang = (chang1.x || chang1.y) ? chang1 : chang2;
+    let newPoint3 = {x: points[3].x + chang.x, y: points[3].y + chang.y}
+    let newPoint2 = {x: points[2].x + chang.x, y: points[2].y + chang.y}
+    let line = {points: [points[0], points[1]]} as geoLine
+    
+    let point3 = getDisVerticalLinePoints(line, points[0], this.thickness)
+      .sort((a, b) => lineDis({points: [a, newPoint3]}) - lineDis({points: [b, newPoint3]}))[0]
+    // let point2 = getLineVerticalPoint(line, points[1], lineVector({points: [points[0], point3]}), this.thickness)
+    let point2 = getDisVerticalLinePoints(line, points[1], this.thickness)
+    .sort((a, b) => lineDis({points: [a, newPoint2]}) - lineDis({points: [b, newPoint2]}))[0]
+
+
+    let [p11, p12] = getDisPointLinePoints({points: [points[0], point3]}, points[0], this.thickness)
+
+    point3 = lineDis({points: [p11, points[3]]}) > lineDis({points: [p12, points[3]]}) ? p12 : p11
+
+    let [p21, p22] = getDisPointLinePoints({points: [points[1], point2]}, points[1], this.thickness)
+    let temp = null
+
+    if (lineDis({points: [p21, points[2]]}) > lineDis({points: [p22, points[2]]})) {
+      point2 = p22
+      temp = p21
+    } else {
+      point2 = p21
+      temp = p22
+    }
+
+    if (Math.abs(lineDis({points: [point2, point3]}) - lineDis({points: [points[0], points[1]]})) > 0.01) {
+      point2 = temp
+    }
+
+    points = [...points]
+    points[2] = point2
+    points[3] = point3
+
+    return this.checkPeripheral(points) ? [point2, point3] : []
+  }
+
+  updatePeripheral(points: Array<point> = this.points) :boolean {
+    let retr = this.getPeripheral(points)
+    
+    // if (retr.length === 0 || !this.checkPeripheral(points.slice(0, 2).concat(retr))) return false
+    this.points[2].x = retr[0].x
+    this.points[2].y = retr[0].y
+    this.points[3].x = retr[1].x
+    this.points[3].y = retr[1].y
+
+    this.readyPoints = points.slice(0, 2).concat(retr)
+
+    return true
+  }
+
+  intercept(trgs: Array<any>, {x, y}, rets) {
+    let ret, upIndex, linePoint, pointIndex;
+
+    if (type.isUndefined(x) || type.isUndefined(y)) return true
+    if ((ret = super.intercept(trgs, {x, y}, rets)) as any === false) return ret;
+
+    upIndex = this.points.findIndex(trg => trgs.some(p => p === trg))
+    pointIndex = this.attachment.points.findIndex(point => linePoint = trgs.find(trg => trg === point))
+
+
+    // 如果是线条连带的
+    if (!~upIndex && linePoint) {
+
+      let points = []
+      points[this.points.indexOf(this.linePoints[0])] = (ret as any).__points[0]
+      points[this.points.indexOf(this.linePoints[1])] = (ret as any).__points[1]
+      points.push(this.points[2], this.points[3])
+      this.firstLinePoints[pointIndex] = linePoint
+      let readyPoints = this.getPeripheral(points)
+
+      this.cacheVers = [
+        lineVector({points: [this.points[1], this.points[2]]}),
+        lineVector({points: [this.points[0], this.points[3]]})
+      ]
+      if (readyPoints.length === 0) return false
+    // 如果是拖拽本身
+    } else {
+      let points = [...this.points] as Array<point>
+      if (!~upIndex) return ret
+      points[upIndex] = {...rets[trgs.indexOf(points[upIndex])]}
+
+      if (upIndex < 2) {
+        if (!this.updatePeripheral(points)) return false
+      } else {
+        let consultLine, otherLine, origin, current, other, cahceVer
+        if (upIndex === 2) {
+          consultLine = {points: [this.points[1], this.points[2]]}
+          otherLine = {points: [this.points[0], this.points[3]]}
+          origin = this.points[2]
+          current = 2
+          other = 3
+          cahceVer = this.cacheVers[1]
+        } else {
+          otherLine = {points: [this.points[1], this.points[2]]}
+          consultLine = {points: [this.points[0], this.points[3]]}
+          origin = this.points[3]
+          current = 3
+          other = 2
+          cahceVer = consultLine
+          cahceVer = this.cacheVers[0]
+        }
+        let conV = lineVector(consultLine)
+
+        if (conV.x - cahceVer.x < 0.01 && conV.y - cahceVer.y < 0.01) {
+          let moveCurrent = getLinePoint(consultLine, {x, y})
+          let moveOther1 = getLinePoint(otherLine, {x, y})
+          let ox = otherLine.points[1].x + x - origin.x 
+          let oy = otherLine.points[1].y + y - origin.y 
+          let moveOther2 = getLinePoint(otherLine, {x: ox, y: oy})
+          let moveOther = Math.abs(this.moveLineDis - lineDis({points: [moveCurrent, moveOther1]})) > 
+            Math.abs(this.moveLineDis - lineDis({points: [moveCurrent, moveOther2]})) ?  
+            moveOther2 : moveOther1
+      
+          points[current] = moveCurrent as Point
+          points[other] = moveOther as Point
+        }
+        this.readyPoints = points
+        // this.nextTick(() => {
+        //   if (this.tick > this.maxTick) {
+        //     this.thickness = this.maxTick - 0.011
+        //     this.updatePeripheral()
+        //   }
+        // })
+
+        return true
+      }
+    }
+    return ret;
+  }
+
+  dragStart(ev) {
+    let index = this.lines.indexOf(ev.target)
+    if (index < 3) {
+      this.__index = index
+    } else {
+      this.__index = -1
+    }
+    this.__startPoints = this.points.map(point => ({x: point.x, y: point.y}))
+  }
+
+  drag(offset) {
+    let move = { x: offset.x *this.multiple, y: offset.y *this.multiple }
+    let linePoints = ~this.__index ? [this.points[this.__index]] : this.linePoints
+    let startPoints = ~this.__index? [this.__startPoints[this.__index]] : this.__startPoints
+
+    if (this.__index < 2) {
+      linePoints.forEach((point, i) => {
+        point.x = startPoints[i].x + move.x
+        point.y = startPoints[i].y + move.y
+      })
+    } else {
+      // 如果拖拽的是外围边
+      let point3 = getLinePoint({points: [this.points[0], this.points[3]]}, { x: this.__startPoints[3].x + move.x, y: this.__startPoints[3].y + move.y })
+      let point2 = getLinePoint({points: [this.points[1], this.points[2]]}, { x: this.__startPoints[2].x + move.x, y: this.__startPoints[2].y + move.y })
+
+      if (this.checkPeripheral(this.points.slice(0, 2).map(point => ({x: point.x, y: point.y})).concat([point2, point3]))) {
+        this.points[3].x = point3.x
+        this.points[3].y = point3.y
+        this.points[2].x = point2.x
+        this.points[2].y = point2.y
+      }
+    }
+  }
+
+  dragEnd() {
+    delete this.dragStartPoints
+    delete this.__index
+    this.updateThickness()
+    this.__leaveHandle()
+    this.__lineLeave()
+    super.dragEnd()
+  }
+
+  delEvent() {
+    this.wrapPath.removeEventListener('mouseenter', this.__enterHandle, false)
+    this.wrapPath.removeEventListener('mouseleave', this.__leaveHandle, false)
+    this.lines.forEach(line => {
+      line.removeEventListener('mouseenter', this.__lineEnter, false)
+      line.removeEventListener('mouseleave', this.__lineLeave, false)
+      line.removeEventListener('click', this.__lineClick, false)
+    })
+  }
+
+  destroy() {
+    this.delEvent()
+    super.destroy()
+  }
+}
+
+export default Column

BIN
src/CAD/core/architecture/door/cad_enter@3x.png


BIN
src/CAD/core/architecture/door/cad_enter_dark@3x.png


+ 188 - 0
src/CAD/core/architecture/door/index.ts

@@ -0,0 +1,188 @@
+import Line from '../../core/wallline'
+import {SVGURI} from '../../constant/Element'
+import {point} from '../../core/fixedpoint'
+import { ElementProps } from '../../core/element'
+import LineArch, {LineArchProps} from '../linearch'
+import LinePoint from '../../core/linepoint'
+import {lineDis, getDisVectorPoints, verticalLine, getDisVerticalLinePoints, isClockWise, pointInside, lineDeg, faceCenter, lineStretch, lineVector} from '../../geometry'
+import * as defaultIcon from './cad_enter@3x.png'
+import * as darkIcon from './cad_enter_dark@3x.png'
+
+let icon = {
+  defaultIcon, darkIcon
+}
+
+export interface DoorStyle {
+  fill?: string,
+  stroke?: string,
+  outWidth?: number,
+  foorWidth?: number,
+  foorColor?: string
+  linecap?: string,
+  icon?: string
+}
+
+interface PublicProps extends DoorStyle { hover?: DoorStyle, attachment: Line, within?: number }
+interface ArchProps extends LineArchProps,PublicProps {}
+export interface DoorProps extends PublicProps, ElementProps {
+  points: [point, point],
+  top: number,
+  bottom: number,
+  show?: boolean,
+  start?: boolean
+}
+
+/**
+ * @category core
+ * @subcategory CAD_Architecture
+ */
+class Door extends LineArch<ArchProps> {
+  linePoints: [LinePoint, LinePoint]
+  arc: SVGPathElement
+  outLine: SVGPathElement
+  image: SVGImageElement
+  rel: SVGGElement
+  init: DoorStyle
+  start: boolean
+  showStart: boolean
+  ctl: [point]
+
+  constructor({points, fill = 'rgba(0,0,0,0)', icon = 'defaultIcon', stroke = 'rgba(255,255,255,0.5)', outWidth = 1, foorWidth = 6, linecap='square', foorColor = 'rgba(0,0,0,0)', hover, within = 0, start = false, ...args}: DoorProps) {
+    let init = {fill, stroke, outWidth, foorWidth, foorColor, linecap}
+    hover = hover || {...init, fill: 'rgba(243, 255, 0, 0.8)'}
+    super({linePoints: points, hover, within, start, icon, ...init, ...args})
+    this.init = init
+    this.showStart = true
+  }
+
+  grentNode () {
+    let node = document.createElementNS(SVGURI, 'g')
+
+    this.arc = document.createElementNS(SVGURI, 'path')
+    this.outLine = document.createElementNS(SVGURI, 'path')
+    this.image = document.createElementNS(SVGURI, 'image')
+
+    // this.image.setAttribute('xlink:href', icon)
+    // this.image.href.baseVal = icon
+
+    node.appendChild(this.outLine)
+    node.appendChild(this.arc)
+    node.appendChild(this.image)
+
+    this.nextTick(() => {
+      node.appendChild(this.linePoints[0].real)
+      node.appendChild(this.linePoints[1].real)
+    })
+
+    return node
+  }
+
+  getCalcPoint(r = lineDis({points: this.linePoints})) {
+    let p1 = this.linePoints[0]
+    let [p3] = getDisVectorPoints(verticalLine(this.attachment), p1, r)
+    return p3
+  }
+
+  getDrawArgs(): [point, point, point, number, boolean] {
+    let p1, p2;
+    if (this.within === 0 || this.within === 2) {
+      [p1, p2] = this.linePoints
+    } else {
+      [p2, p1] = this.linePoints
+    }
+
+    // let [p2, p1] = this.linePoints
+    let r = lineDis({points: this.linePoints})
+
+    let [p31, p32] = getDisVerticalLinePoints(this.attachment, p1, r)
+    
+    let p3 = this.within === 0 || this.within === 1 ? p31 : p32
+    // console.log(p1.x, p1.y)
+    return [p1, p2, p3, r, isClockWise([p1, p2, p3])]
+  }
+
+  update() {
+    this.real.style.display = this.show ? 'initial' : 'none'
+    let [p1, p2, p3, r, clockwise] = this.getDrawArgs()
+    if (isNaN(p3.x)) {
+      this.destroy()
+      return;
+    }
+    this.ctl = [p3]
+    this.arc.setAttribute('d', `M ${p1.x} ${p1.y} L ${p3.x} ${p3.y} A ${r} ${r} 0 0 ${clockwise ? '1' : '0'} ${p2.x} ${p2.y} Z`)
+    this.arc.setAttribute('stroke-width', (this.outWidth * this.multiple).toString())
+    this.arc.setAttribute('stroke', this.stroke)
+    this.arc.setAttribute('fill', this.fill)
+
+    if (this.icon !== this.firstIcon) {
+      this.image.href.baseVal = icon[this.icon]
+      this.firstIcon = this.icon
+    }
+
+    this.outLine.setAttribute('stroke', this.foorColor)
+    this.outLine.setAttribute('fill', this.fill)
+    this.outLine.setAttribute('stroke-width', (this.foorWidth * this.multiple).toString())
+    this.outLine.setAttribute('stroke-linecap', this.linecap)
+    this.outLine.setAttribute('d', `M ${this.linePoints[0].x} ${this.linePoints[0].y} L ${this.linePoints[1].x} ${this.linePoints[1].y}`)
+    this.attachment.update()
+
+    if (this.start && this.showStart) {
+      let x = lineDis({points: this.linePoints})
+      let { points } = lineStretch({points: this.linePoints}, x / 2)
+      let width = lineDis({points: points})
+      if (isNaN(width)) return;
+      let height = width * (132 / 33)
+      let start = points[1]
+      let end = start === points[1] ? points[0] : points[1]
+      let points1 = getDisVerticalLinePoints({points: this.linePoints}, start, height)
+      let ctl1 = lineDis({points: [points1[0], p3]}) > lineDis({points: [points1[1], p3]}) ? points1[0] : points1[1]
+      let points2 = getDisVerticalLinePoints({points: this.linePoints}, end, height)
+      let ctl2 = lineDis({points: [points2[0], p3]}) > lineDis({points: [points2[1], p3]}) ? points2[0] : points2[1]
+      
+      
+      let center = faceCenter([start, ctl1, ctl2, end ])
+      let deg = lineDeg({points: [start, ctl1]}) + 90;
+
+      this.image.setAttribute('width', width.toString())
+      this.image.setAttribute('height', height.toString())
+      this.image.setAttribute('x', (center.x - width / 2).toString())
+      this.image.setAttribute('y', (center.y - height / 2).toString())
+      this.image.setAttribute('transform', `rotate(${deg}, ${center.x}, ${center.y})`)
+      this.image.style.display = 'block'
+    } else {
+      this.image.style.display = 'none'
+    }
+  }
+  
+  dragStart(ev) {
+    this.__index = ev.target === this.linePoints[0].real ? 0 : 
+      ev.target === this.linePoints[1].real ? 1 : -1
+    this.__startPoints = this.linePoints.map(point => ({x: point.x, y: point.y}))
+  }
+
+  drag(offset) {
+    let linePoints = ~this.__index ? [this.linePoints[this.__index]] : this.linePoints
+    let startPoints = ~this.__index? [this.__startPoints[this.__index]] : this.__startPoints
+
+    linePoints.forEach((point, i) => {
+      point.x = startPoints[i].x + offset.x * this.multiple
+      point.y = startPoints[i].y + offset.y * this.multiple
+    })
+  }
+
+  dragEnd() {
+    delete this.dragStartPoints
+    delete this.__index
+    super.dragEnd()
+  }
+
+  setHoverStyle() {
+    Object.keys(this.hover).forEach(k => this[k] = this.hover[k])
+  }
+
+  setUnHoverStyle() {
+    Object.keys(this.init).forEach(k => this[k] = this.init[k])
+  }
+}
+
+export default Door

+ 123 - 0
src/CAD/core/architecture/groundCase.ts

@@ -0,0 +1,123 @@
+import {SVGURI} from '../constant/Element'
+import {point} from '../core/fixedpoint'
+import LineArch, {LineArchProps} from './linearch'
+import {ElementProps} from '../core/element'
+import {getDisPointLinePoints, lineDis, getLineDisSelectPoint, getDisVerticalLinePoints, lineCenter} from '../geometry'
+import Line from '../core/wallline'
+
+export interface CaseProps extends PublicProps {
+  points: [point, point],
+}
+
+export interface PublicProps extends ElementProps {
+  top: number,
+  bottom: number,
+  attachment: Line,
+  width?: number
+}
+interface ArchProps extends LineArchProps,PublicProps {
+  fill: string
+}
+
+/**
+ * @category core
+ * @subcategory CAD_Architecture
+ */
+class GroundCase extends LineArch<ArchProps> {
+  clip1: SVGPathElement
+  clip2: SVGPathElement
+  clip3: SVGPathElement
+  clip4: SVGPathElement
+
+  constructor({points, width = 3, ...args}: CaseProps) {
+    // points[0].fillColor = 'rgba(0,0,0,0)'
+    // points[1].fillColor = 'rgba(0,0,0,0)'
+    super({linePoints: points, fill: 'rgba(255,255,255,0.5)', width, ...args})
+  }
+
+  grentNode () {
+    let node = document.createElementNS(SVGURI, 'g')
+
+    this.clip1 = document.createElementNS(SVGURI, 'path')
+    this.clip2 = document.createElementNS(SVGURI, 'path')
+    this.clip3 = document.createElementNS(SVGURI, 'path')
+    this.clip4 = document.createElementNS(SVGURI, 'path')
+    this.clip5 = document.createElementNS(SVGURI, 'path')
+    this.clip1.setAttribute('stroke', this.fill)
+    this.clip2.setAttribute('stroke', this.fill)
+    this.clip3.setAttribute('stroke', this.fill)
+    this.clip4.setAttribute('stroke', this.fill)
+    this.clip5.setAttribute('stroke', this.fill)
+
+    node.appendChild(this.clip1)
+    node.appendChild(this.clip2)
+    node.appendChild(this.clip3)
+    node.appendChild(this.clip4)
+    node.appendChild(this.clip5)
+
+    this.nextTick(() => {
+      node.appendChild(this.linePoints[0].real)
+      node.appendChild(this.linePoints[1].real)
+    })
+    return node
+  }
+  
+  setHoverStyle() {
+    this.fill = 'rgba(243, 255, 0, 0.8)'
+  }
+
+  setUnHoverStyle() {
+    this.fill = '#fff'
+  }
+
+
+  update() {
+    let [p1, p3] = getDisVerticalLinePoints(this.attachment, this.linePoints[0], this.width * 0.4 * this.multiple)
+    let [p2, p4] = getDisVerticalLinePoints(this.attachment, this.linePoints[1], this.width * 0.4 * this.multiple)
+    let [p5, p7] = getDisVerticalLinePoints(this.attachment, this.linePoints[0], this.width * 0.15 * this.multiple)
+    let [p6, p8] = getDisVerticalLinePoints(this.attachment, this.linePoints[1], this.width * 0.15 * this.multiple)
+    let p9 = lineCenter({points: [p5, p6]})
+    let p10 = lineCenter({points: [p7, p8]})
+    // let [p9, p10] = getDisVerticalLinePoints(this.attachment, lineCenter(this.attachment), this.width * 0.15 * this.multiple)
+
+    if (isNaN(p1.x)) return;
+
+    this.clip1.setAttribute('stroke-width', (this.multiple).toString())
+    this.clip1.setAttribute('d', `M ${p1.x} ${p1.y} L ${p2.x} ${p2.y}`)
+    this.clip2.setAttribute('stroke-width', (this.multiple).toString())
+    this.clip2.setAttribute('d', `M ${p3.x} ${p3.y} L ${p4.x} ${p4.y}`)
+    this.clip3.setAttribute('stroke-width', (this.multiple).toString())
+    this.clip3.setAttribute('d', `M ${p5.x} ${p5.y} L ${p6.x} ${p6.y}`)
+    this.clip4.setAttribute('stroke-width', (this.multiple).toString())
+    this.clip4.setAttribute('d', `M ${p7.x} ${p7.y} L ${p8.x} ${p8.y}`)
+    this.clip5.setAttribute('stroke-width', (6*this.multiple).toString())
+    this.clip5.setAttribute('d', `M ${p9.x} ${p9.y} L ${p10.x} ${p10.y}`)
+    this.clip1.setAttribute('stroke', this.fill)
+    this.clip2.setAttribute('stroke', this.fill)
+    this.clip3.setAttribute('stroke', this.fill)
+    this.clip4.setAttribute('stroke', this.fill)
+    this.clip5.setAttribute('stroke', this.fill)
+    this.attachment.update()
+  }
+
+  
+  dragStart(ev) {
+    this.move = this.linePoints[0].real === ev.target || this.linePoints[1].real === ev.target
+    this.dragStartPoints = this.linePoints.map(point => ({x: point.x, y: point.y}))
+  }
+
+  drag(offset) {
+    if (this.move) return;
+    this.linePoints.forEach((point, i) => {
+      point.x = this.dragStartPoints[i].x + offset.x * this.multiple
+      point.y = this.dragStartPoints[i].y + offset.y * this.multiple
+    })
+  }
+
+  dragEnd() {
+    this.move = false
+    delete this.dragStartPoints
+  }
+}
+
+export default GroundCase

+ 280 - 0
src/CAD/core/architecture/linearch.ts

@@ -0,0 +1,280 @@
+import {CADElement, ElementProps} from '../core/element'
+import LinePoint from '../core/linepoint'
+import {point} from '../core/fixedpoint'
+import Line from '../core/wallline'
+import {
+  Line as GeoLine,
+  lineDis,
+  isContainPoint,
+  getDisPointLinePoints,
+  pointLineDis,
+} from '../geometry'
+import {type} from '../util'
+
+
+export interface LineArchProps extends ElementProps {
+  [k: string]: any
+  linePoints: [point, point],
+  attachment: Line,
+  minWidth?: number,
+  show?: boolean
+}
+
+/**
+ * @category core
+ * @subcategory CAD_Architecture
+ */
+class LineArch extends CADElement<LineArchProps> {
+  linePoints: [LinePoint, LinePoint]
+
+  // 同一线条所有建筑归为一个数组
+  static attaArch = new Map<Line, Array<LineArch>>()
+
+  constructor({minWidth = 0.1, deleteWidth = 0.05, show = true, ...args}: LineArchProps) {
+    super({...args,  show})
+    this.zIndex = 1
+    this.linePoints = (this.linePoints.map(point => {
+      let lp = new LinePoint({...point, renderer: this.renderer}, this.attachment);
+      lp.click = () => this.changeSelect(true)
+      return lp;
+    })) as [LinePoint, LinePoint]
+    this.minWidth = minWidth
+    this.deleteWidth = deleteWidth
+    this.real.setAttribute('class', 'variable')
+
+    
+    if (LineArch.attaArch.get(this.attachment)) {
+      LineArch.attaArch.get(this.attachment).push(this)
+    } else {
+      LineArch.attaArch.set(this.attachment, [this])
+    }
+
+
+    this.attachment.update()
+  }
+
+  // 设置新的附属线条
+  setAttachment(newAttachment: Line = this.attachment) {
+    console.error('set new attach')
+    if (this.attachment === newAttachment) return;
+    let oldArchs = LineArch.attaArch.get(this.attachment)
+    let newArchs = LineArch.attaArch.get(newAttachment) || 
+      LineArch.attaArch.set(newAttachment, []).get(newAttachment);
+
+    oldArchs.splice(oldArchs.indexOf(this), 1)
+    newArchs.push(this)
+
+    this.linePoints.forEach(point => {
+      point.line = newAttachment
+    })
+
+    this.attachment = newAttachment
+  }
+  
+  // 建筑端点离墙端点限制
+  checkPointBorder(point: point, line:GeoLine = this.attachment) :boolean {
+    // return !isContainPoint(line, point)
+    
+    if (lineDis({points: [line.points[0], point]}) < 0.08) {
+      return true
+    }
+
+    if (lineDis({points: [line.points[1], point]}) < 0.08) {
+      return true
+    }
+  }
+
+  // 点是否在沿线上
+  checkPoint(point: point, line:GeoLine = this.attachment) :boolean {
+    return !isContainPoint(line, point)
+  }
+
+  // 宽度是否合格
+  checkWidth(points: [point, point] = this.linePoints) : boolean {
+    return lineDis({points}) < this.minWidth
+  }
+  
+  // 检测同一线条所有建筑是否重叠
+  checkPointOverlapAttaArch(archs: Array<LineArch> = LineArch.attaArch.get(this.attachment)) : boolean {
+    if (archs.length <= 1) return false
+
+    return archs.some(arch => 
+      archs.some(carch => 
+        arch !== carch && (
+          isContainPoint({points: carch.linePoints}, arch.linePoints[0]) ||
+          isContainPoint({points: carch.linePoints}, arch.linePoints[1])
+        )
+      )
+    )
+  }
+
+  // 检测同一线条所有建筑是否在线上
+  checkLineAllPoint(archs: Array<LineArch> = LineArch.attaArch.get(this.attachment), line: GeoLine = this.attachment) : boolean {
+    return archs.length !== 0 && archs.some(arch =>
+      !isContainPoint(line, arch.linePoints[0]) ||
+      !isContainPoint(line, arch.linePoints[1])
+    )
+  }
+
+  // 获取线条内所有建筑,并且替换掉指定arch的points
+  private getLineNewAll(points: [point, point] = this.linePoints, repArch = this) {
+    let archs = [...LineArch.attaArch.get(this.attachment)]
+    archs.splice(archs.indexOf(repArch), 1, { linePoints: points } as unknown as LineArch)
+    return archs
+  }
+
+  // 检测是否合格通过
+  qualified(points: [point, point] = this.linePoints, line: GeoLine = this.attachment) {
+    return !(
+      this.checkPointBorder(points[0], line) ||
+      this.checkPointBorder(points[0], line) ||
+      this.checkPoint(points[0], line) ||
+      this.checkPoint(points[1], line) ||
+      this.checkWidth(points)
+    )
+  }
+
+  // 宿主线条点变化引起变化
+  lineChange (point = this.attachment.points[0], move: point = point) : {line: GeoLine, points: [point, point]} {
+    let {x, y} = move
+    let index = this.attachment.points.findIndex(item => item === point);
+    // 获取当前变化线条
+    let line = { points: index === 0 ? 
+      [{x, y}, this.attachment.points[1]] :  [this.attachment.points[0], {x, y}] 
+    } as GeoLine;
+
+
+
+    let dis1 = lineDis({points: [line.points[index], this.linePoints[0]] })
+    let [p11, p12] = getDisPointLinePoints(line, line.points[index], dis1)
+
+    let point1 = lineDis({points: [p11, this.linePoints[0]]}) > lineDis({points: [p12, this.linePoints[0]]}) ? p12 : p11
+
+    let dis2 = lineDis({points: [line.points[index], this.linePoints[1]] })
+    let [p21, p22] = getDisPointLinePoints(line, line.points[index], dis2)
+
+    let point2 = lineDis({points: [p21, this.linePoints[1]]}) > lineDis({points: [p22, this.linePoints[1]]}) ? p22 : p21
+
+
+
+    
+    // 让linepoint类检查一下
+    let points = (
+      lineDis({points: [this.linePoints[0], point1]}) < lineDis({points: [this.linePoints[0], point2]}) ?
+      [point1, point2] : [point2, point1]
+    ).map(
+      (point) => {
+        let p = LinePoint.prototype.getLineInsertPoint.call({...this.linePoints[0], line}, point)
+
+        if (pointLineDis(this.attachment, p) > 0.1) {
+          return lineDis({points: [this.attachment.points[0], p]})  < lineDis({points: [this.attachment.points[1], p]}) ? 
+            {x: this.attachment.points[0].x, y: this.attachment.points[0].y} :
+            {x: this.attachment.points[1].x, y: this.attachment.points[1].y}
+        } else {
+          return p
+        }
+      }
+    ) as [point, point]
+
+
+    return { line, points }
+  }
+
+
+  // 建筑沿线点变化引起变化
+  pointChange(point, {x, y}): [point, point] {
+    let index = this.linePoints.findIndex(p => p === point)
+    return index === 0 ? 
+      [{x, y}, this.linePoints[1]] : [this.linePoints[0], {x, y}]
+  }
+
+  lineChangeCheck(points, line, archs) {
+    return this.qualified(points, line) && !this.checkLineAllPoint(archs, line) && !this.checkPointOverlapAttaArch(archs)
+  }
+
+  // 当变化时
+  intercept(trgs: Array<any>, {x, y}, rets) {
+    if (type.isUndefined(x) || type.isUndefined(y)) return true;
+    let seftPointIndex, linePoint
+
+    // 如果是线条而引起的变化
+    if (linePoint = this.attachment.points.find(point => trgs.some(trg => trg === point))) {
+      let {points, line} = this.lineChange(linePoint, {x, y})
+      // 把所有沿线建筑改为最新值
+      let arches: Array<LineArch> = trgs
+        .filter(trg => trg instanceof LineArch && trg.attachment.id === this.attachment.id);
+
+      arches = Array.from(new Set(arches))
+
+      this.attachment.nextTick(() => {
+        if (!this.attachment || !this.attachment.points) {
+          return this.destroy()
+        }
+        let {points, line} = this.lineChange(linePoint, {x: linePoint.x, y: linePoint.y})
+
+        points.forEach((point, i) => {
+          if (point) {
+            this.linePoints[i].x = point.x
+            this.linePoints[i].y = point.y
+          }
+        })
+        this.nextTick(() => {
+          if (this.linePoints && this.linePoints.length) {
+            if (lineDis({points: this.linePoints}) <= this.deleteWidth) {
+              this.destroy()
+            }
+          }
+        })
+      })
+      this.attachment.update()
+      // return false
+      return {__points: points}
+      // return true
+    // 如果是linePoint引起的变化
+      
+    } else if ( ~(seftPointIndex = trgs.findIndex(trg => this.linePoints.some(p => p === trg))) ) {
+      // let ret = rets[seftPointIndex]
+      // let points = this.pointChange(trgs[seftPointIndex], ret)
+      // this.attachment.update()
+
+      // return true
+      // return !(
+      //   this.checkPointBorder(ret) ||
+      //   this.checkPoint(ret) || 
+      //   this.checkWidth(points) || 
+      //   this.checkPointOverlapAttaArch(this.getLineNewAll(points))
+      // )
+    }
+  }
+  
+  dragEnd() {
+    this.nextTick(() => {
+      if (lineDis({points: this.linePoints}) <= this.deleteWidth) {
+        this.destroy()
+      }
+    })
+  }
+
+  destroy() {
+    let Archs = LineArch.attaArch.get(this.attachment)
+    if (Archs) {
+      Archs.splice(Archs.indexOf(this), 1)
+      this.linePoints[0].destroy()
+      this.linePoints[1].destroy()
+      this.attachment = null
+      this.linePoints = null
+      this.update = null
+      super.destroy()
+      this.attachment.update()
+    }
+  }
+}
+
+
+type LineArchTemp<T={}> = LineArch & T
+
+export type LineArchTs = ({ new <T={}, K = any>(data: T): LineArchTemp<T> }) & {
+  attaArch: Map<Line, Array<LineArch>>
+}
+export type LineArchClass = LineArch
+export default LineArch as LineArchTs

+ 152 - 0
src/CAD/core/architecture/slideDoor.ts

@@ -0,0 +1,152 @@
+import Door, { DoorProps } from './door/index'
+import { SVGURI } from '../constant/Element'
+import { lineDis, verticalLine, getLineDisSelectPoint } from '../geometry'
+
+interface SlideDoorProps extends DoorProps {
+  clipColor?: string,
+  within: number,
+  bwithin: number
+}
+
+/**
+ * @category core
+ * @subcategory CAD_Architecture
+ */
+class SlideDoor extends Door {
+  layer: SVGPathElement
+  clip1: SVGPathElement
+  clip2: SVGPathElement
+  path1: SVGPathElement
+  path2: SVGPathElement
+  left: SVGRectElement
+  right: SVGRectElement
+
+  constructor({points, foorWidth = 6, foorColor = 'rgba(255,255,255,0.5)', clipColor = 'rgba(0,0,0,1)', linecap = "square", within = 0, ...args}: SlideDoorProps) {
+    points[0].fillColor = 'rgba(0,0,0,0)'
+    points[1].fillColor = 'rgba(0,0,0,0)'
+    super({...args, points, foorWidth, foorColor, within })
+    this.clipColor = clipColor
+  }
+
+  
+  setHoverStyle() {
+    this.clipColor = 'rgba(243, 255, 0, 0.8)'
+    this.foorColor = 'rgba(243, 255, 0, 0.8)'
+  }
+
+  setUnHoverStyle() {
+    this.clipColor = 'rgba(0,0,0,1)'
+    this.foorColor = 'rgba(255,255,255,0.5)'
+  }
+
+  grentNode() {
+    
+    let node = document.createElementNS(SVGURI, 'g')
+
+    node.innerHTML = `
+      <path class="slide-door-1" />
+      <path class="slide-door-2" />
+      <path class="slide-door-clip-1" />
+      <path class="slide-door-clip-2" />
+      <rect width="0.00001" height="0.00001" class="slide-door-left" />
+      <rect width="0.00001" height="0.00001" class="slide-door-right" />
+    `
+    
+    this.clip1 = node.querySelector('.slide-door-clip-1')
+    this.clip2 = node.querySelector('.slide-door-clip-2')
+    this.path1 = node.querySelector('.slide-door-1')
+    this.path2 = node.querySelector('.slide-door-2')
+    this.left = node.querySelector('.slide-door-left')
+    this.right = node.querySelector('.slide-door-right')
+
+    this.nextTick(() => {
+      node.appendChild(this.linePoints[0].real)
+      node.appendChild(this.linePoints[1].real)
+
+      this.linePoints.forEach(point => {
+        point.changeSelect = (isSelect) => {
+          isSelect && this.changeSelect(isSelect)
+        }
+      })
+    })
+    return node
+  }
+
+  update() {
+    // if (!this.path1 || !this.clip1 || !this.path2)
+    let width = this.foorWidth * this.multiple
+    let lineWidth = lineDis({points: this.linePoints})
+    let padding =  2 * this.multiple
+    let lr = lineWidth * 0.6
+    let p1, p2;
+    if (this.within) {
+      [p1, p2] = this.linePoints
+    } else {
+      [p2, p1] = this.linePoints
+    }
+
+    if (isNaN(p1.x)) return;
+    
+    let leftr = getLineDisSelectPoint({points: this.linePoints}, p1, lr)
+    let leftrc = getLineDisSelectPoint({points: this.linePoints}, p1, lr - padding)
+    let rightl = getLineDisSelectPoint({points: this.linePoints}, p2, lr)
+    let rightlc = getLineDisSelectPoint({points: this.linePoints}, p2, lr - padding)
+    let vv = verticalLine({points: this.linePoints})
+    let lrwidth = width / 2
+    let marginWidth = lrwidth - padding
+    let margin = ((width - lrwidth) / 2) - 0.0001 * this.multiple
+
+    let startl = {
+      x: p1.x + vv.x * margin,
+      y: p1.y + vv.y * margin
+    }
+    let endl = {
+      x: leftr.x + vv.x * margin,
+      y: leftr.y + vv.y * margin,
+    }
+    let endlc = {
+      x: leftrc.x + vv.x * margin,
+      y: leftrc.y + vv.y * margin,
+    }
+    let startr = {
+      x: p2.x - vv.x * margin,
+      y: p2.y - vv.y * margin
+    }
+    let endr = {
+      x: rightl.x - vv.x * margin,
+      y: rightl.y - vv.y * margin,
+    }
+    let endrc = {
+      x: rightlc.x - vv.x * margin,
+      y: rightlc.y - vv.y * margin,
+    }
+
+    this.path1.setAttribute('stroke', this.foorColor)
+    this.path1.setAttribute('stroke-width', lrwidth.toString())
+    this.path1.setAttribute('d', `M ${startl.x} ${startl.y} L ${endl.x} ${endl.y}`)
+    
+    this.clip1.setAttribute('stroke', this.clipColor)
+    this.clip1.setAttribute('stroke-width', (lrwidth - this.bwithin * this.multiple) .toString())
+    this.clip1.setAttribute('d', `M ${startl.x} ${startl.y} L ${endlc.x} ${endlc.y}`)
+
+    this.path2.setAttribute('stroke', this.foorColor)
+    this.path2.setAttribute('stroke-width', (lrwidth).toString())
+    this.path2.setAttribute('d', `M ${startr.x} ${startr.y} L ${endr.x} ${endr.y}`)
+
+    this.clip2.setAttribute('stroke', this.clipColor)
+    this.clip2.setAttribute('stroke-width', (lrwidth - this.bwithin * this.multiple) .toString())
+    this.clip2.setAttribute('d', `M ${startr.x} ${startr.y} L ${endrc.x} ${endrc.y}`)
+
+    this.left.setAttribute('x', p1.x.toString())
+    this.left.setAttribute('y', p1.y.toString())
+    this.left.setAttribute('stroke-width', width.toString())
+    this.left.setAttribute('stroke', 'rgba(0,0,0,0)')
+    this.right.setAttribute('x', p2.x.toString())
+    this.right.setAttribute('y', p2.y.toString())
+    this.right.setAttribute('stroke-width', width.toString())
+    this.right.setAttribute('stroke', 'rgba(0,0,0,0)')
+    this.attachment.update()
+  }
+}
+
+export default SlideDoor

+ 86 - 0
src/CAD/core/base/cad.ts

@@ -0,0 +1,86 @@
+import {getMapMultipleWH} from '../util'
+import Processing, {ProcessingTS, StorageProps, Data} from './processing/index'
+import {CADElement} from '../core/element'
+
+export type Screen = {x: number, y: number}
+interface CADProps extends StorageProps {
+  padding?: number
+}
+
+
+class CAD {
+  isDestroy: boolean
+  padding: number
+  resizeHandle: () => void
+  processing: ProcessingTS
+
+  constructor({dom, padding = 20}: CADProps) {
+    this.processing = new Processing({dom})
+    this.processing.cad = this as any
+    this.padding = padding
+    this.isDestroy = false
+    this.resizeHandle = () => this.processing.data && this.adapt(this.processing.data)
+    this.init()
+  }
+
+  adapt(
+    data: Data = this.processing.data, 
+    width = this.processing.render.layer.offsetWidth || parseInt(getComputedStyle(this.processing.render.layer).width) || 100,
+    height = this.processing.render.layer.offsetHeight || parseInt(getComputedStyle(this.processing.render.layer).height) || 100
+  ) {
+    if (data.vertex.length === 0) {
+      console.log('------------1')
+      this.processing.render.props = getMapMultipleWH(width, height, {
+        vertex: [
+          {x: -15, y: -15},
+          {x: -15, y: 15},
+          {x: 15, y: -15},
+          {x: 15, y: 15}
+        ]
+      }, this.padding)
+    } else {
+      console.log('------------2')
+      if (width && height) {
+        this.processing.render.props = getMapMultipleWH(width, height, data, this.padding)
+      } 
+
+      if (this.processing.render.props.scale <= 0){
+        this.processing.render.props = {
+          width: 0,
+          height: 0,
+          multiple: 0,
+          scale: 1,
+          top: 0,
+          left: 0
+        }
+      }
+    }
+    // this.processing.render.props = {"width":10.066168623265742,"height":9,"multiple":0.0096051227321238,"left":1.0652498723957131,"top":3.9390821771611524,"scale":5.201832061068706}
+    this.processing.render.adaptLayer(width, height)
+    let eles = []
+
+    this.processing.attrs.forEach(attr => eles.concat(this.processing[attr].map(({ele}) => ele)))
+  }
+
+
+  init() {
+    window.addEventListener('resize', this.resizeHandle, false)
+  }
+
+  destroy() {
+    window.removeEventListener('resize', this.resizeHandle, false)
+    this.processing.destroy()
+    this.processing = null
+    this.isDestroy = true
+
+    for (let key in this) {
+      if (typeof this[key] === 'function') {
+        (this[key] as any) = () => {}
+      } else {
+        delete this[key]
+      }
+    }
+  }
+}
+
+export default CAD

+ 553 - 0
src/CAD/core/base/processing/bk.ts

@@ -0,0 +1,553 @@
+import Render from '../renderer'
+import Point from '../../core/point'
+import WallLine from '../../core/wallline'
+import Casement from '../../architecture/casement'
+import Door from '../../architecture/door/index'
+import Column from '../../architecture/column'
+import { CADElementTS } from '../../core/element'
+import { ProcessingTS } from '.'
+
+
+export interface _Point { x: number, y:number, id: number }
+export interface _Line { p1: number, p2: number, id: number }
+export interface _window { pos: Array<number>, line: number, top: number, bottom: number }
+export interface _door { pos: Array<number>, line: number, top: number, bottom: number }
+export interface _column { pos: Array<number>, line: number }
+export interface _hole {pos: Array<number>, top: number}
+export type _Ground = Array<number>
+export interface _room { ground: _Ground, hole: Array<_hole>, top: number, bottom: number }
+export interface Data {vertex: Array<_Point>, surplus: Array<_Point>, wall: Array<_Line>, window: Array<_window>, door: Array<_door>, column: Array<_column>, room: Array<_room>}
+export interface StorageProps{ dom: HTMLElement }
+export interface CADState {elements: Array<Point>}
+export interface rhole extends _hole {bottom: number} 
+
+type Walls = Array<{ id: number; ele: WallLine }>
+type Points = Array<{ id: number; ele: Point }>
+type Cases = Array<{ele: Casement}>
+type Doors = Array<{ele: Door}>
+type Columns = Array<{ele: Column}>
+
+const numRetain = (n: number, m = 2) => Number(n.toFixed(m))
+
+class Processing {
+  data: Data
+  render: Render
+  points: Points
+  lines: Walls
+  cases: Cases
+  doors: Doors
+  columns: Columns
+  
+  constructor({dom}: StorageProps) {
+    this.render = new Render({ layer: dom, processing: (this as unknown as ProcessingTS) })
+    this.points = []
+    this.lines = []
+    this.cases = []
+    this.doors = []
+    this.columns = []
+
+    WallLine.initLines(this.render)
+  }
+
+  getNewPointId = () => Math.max(...this.points.map(({id}) => id)) + 1
+  getNewLineId = () => Math.max(...this.lines.map(({id}) => id)) + 1
+
+  addPoint = ({id, x, y}: _Point) => {
+    let ret = {id, ele: new Point({x, y, renderer: this.render})}
+    this.points.push(ret)
+    this.generateElement(ret.ele)
+    return ret
+  }
+  addLine = ({id, p1, p2}: _Line) => {
+    const rooms = this.data.room
+    const isOut = rooms.some(({hole}) => hole.some(({pos: h}) => ~h.indexOf(p1) && ~h.indexOf(p2)))
+
+    let ret = { 
+      id, 
+      ele: new WallLine({
+        points: [
+          this.points.find(p => p.id === p1).ele, 
+          this.points.find(p => p.id === p2).ele
+        ],
+        renderer: this.render,
+        color: isOut ? 'red': void 0,
+        isOut
+      })
+    }
+    this.lines.push(ret)
+    this.generateElement(ret.ele)
+    return ret
+  }
+  addCase = ({pos, line, top = null, bottom = null}: _window) => {
+    let ret = {
+      ele: new Casement({
+        renderer: this.render,
+        attachment: this.lines.find(({id}) => id === line).ele,
+        points: [{x: pos[0], y: pos[1]}, {x: pos[2], y: pos[3]}],
+        top,
+        bottom
+      })
+    }
+    this.cases.push(ret)
+    this.generateElement(ret.ele)
+    return ret
+  }
+  addDoor = ({pos, line, top = null, bottom = null}: _door) => {
+    let ret = {
+      ele: new Door({
+        renderer: this.render,
+        attachment: this.lines.find(({id}) => id === line).ele,
+        points: [{x: pos[0], y: pos[1]}, {x: pos[2], y: pos[3]}],
+        top, bottom
+      })
+    }
+    this.doors.push(ret)
+    this.generateElement(ret.ele)
+    return ret
+  }
+  addColumn = ({pos, line}: _column) => {
+    let ret = {
+      ele: new Column({
+        renderer: this.render,
+        attachment: this.lines.find(({ id }) => id === line).ele,
+        points: [
+          {x: pos[0], y: pos[1]},
+          {x: pos[2], y: pos[3]},  
+          {x: pos[6], y: pos[7]},
+          {x: pos[4], y: pos[5]}
+        ]
+      })
+    }
+    this.columns.push(ret)
+    this.generateElement(ret.ele)
+    return ret
+  }
+
+  // 获取与当前room有关联的所有room
+  getJoinRooms(room: _room) {
+    const checkRooms = []
+    const queryRooms = (room: _room) => {
+      if (~checkRooms.indexOf(room)) {
+        return []
+      } else {
+        let rooms = this.data.room.filter(({ground}) => ground.some(id => ~room.ground.indexOf(id)))
+        checkRooms.push(room)
+        rooms.forEach(room => rooms.push(...queryRooms(room)))
+        return rooms
+      }
+    }
+
+    return Array.from(new Set(queryRooms(room)))
+  }
+
+  getPointsByRoom (room: _room) {
+    room = this.data.room.find(r => r.ground.join('') === room.ground.join(''))
+
+    if (room) {
+      return room.ground.map(eid => this.points.find(({id}) => id === eid).ele)
+    } else {
+      return []
+    }
+  }
+  
+  getPointsByHole (rhole: rhole) {
+    let rhids = rhole.pos.join('')
+    let hole
+    for (let i = 0; i < this.data.room.length; i++) {
+      hole = this.data.room[i].hole.find(hole => hole.pos.join('') === rhids)
+      if(hole) break
+    }
+
+    if (hole) {
+      return hole.pos.map(eid => this.points.find(({id}) => id === eid).ele)
+    } else {
+      return []
+    }
+  }
+
+  getLinesByRoom (room: _room) {
+    room = this.data.room.find(r => r.ground.join('') === room.ground.join(''))
+
+    if (room) {
+      return this.lines.filter(({ele: line}) => {
+        let p1 = this.points.find(({ele}) => ele === line.points[0]).id
+        let p2 = this.points.find(({ele}) => ele === line.points[1]).id
+        return ~room.ground.indexOf(p1) && ~room.ground.indexOf(p2)
+      }).map(({ele}) => ele)
+    } else {
+      return []
+    }
+  }
+
+  getLinesByHole (rhole: rhole) {
+    let rhids = rhole.pos.join('')
+    let hole
+    for (let i = 0; i < this.data.room.length; i++) {
+      hole = this.data.room[i].hole.find(hole => hole.pos.join('') === rhids)
+      if(hole) break
+    }
+
+    if (hole) {
+      return this.lines.filter(({ele: line}) => {
+        let p1 = this.points.find(({ele}) => ele === line.points[0]).id
+        let p2 = this.points.find(({ele}) => ele === line.points[1]).id
+        return ~hole.pos.indexOf(p1) && ~hole.pos.indexOf(p2)
+      }).map(({ele}) => ele)
+    } else {
+      return []
+    }
+  }
+
+  getRoomsByPoint (point: Point) {
+    let p = this.points.find(({ele}) => ele === point)
+
+    if (p) {
+      return this.data.room.filter(room => ~room.ground.indexOf(p.id))
+    } else {
+      return []
+    }
+  }
+  
+  getHolesByPoint (point: Point): Array<rhole> {
+    let p = this.points.find(({ele}) => ele === point)
+    let holes = []
+
+    if (p) {
+      this.data.room.find(room => {
+        room.hole.forEach(hole => {
+          if (~hole.pos.indexOf(p.id)) {
+            holes.push(hole)
+          }
+        })
+      })
+    } else {
+      console.error(point)
+    }
+    return holes
+  }
+
+  getRoomsByLine (line: WallLine) {
+    let p1 = this.points.find(point => point.ele === line.points[0]).id
+    let p2 = this.points.find(point => point.ele === line.points[1]).id
+    
+    return this.data.room.filter(room => ~room.ground.indexOf(p1) && ~room.ground.indexOf(p2))
+  }
+
+  getHolesByLine (line: WallLine): Array<_hole & {bottom: number}> {
+    let p1 = this.points.find(point => point.ele === line.points[0]).id
+    let p2 = this.points.find(point => point.ele === line.points[1]).id
+    let holes = []
+    this.data.room.forEach(room => {
+      room.hole.forEach(hole => {
+        if (~hole.pos.indexOf(p1) && ~hole.pos.indexOf(p2)) {
+          holes.push(hole)
+        }
+      })
+    })
+
+    return holes
+  }
+
+  getLineId = (line: WallLine) => this.lines.find(({ele}) => ele === line).id
+  getPointId = (point: Point) => this.points.find(({ele}) => ele === point).id
+
+  // 将数据转换为ele
+  toEles (data: Data) {
+    let {vertex, wall, window, door, column} = data
+    this.data = data
+
+    vertex.forEach(this.addPoint)
+    wall.forEach(this.addLine)
+    window.forEach(this.addCase)
+    door.forEach(this.addDoor)
+    column.forEach(this.addColumn)
+
+    this.data.room.forEach(room => {
+      room.hole.forEach(hole => {
+        Object.defineProperty(hole, 'bottom', {
+          get: () => room.bottom
+        })
+      })
+    })
+    this.referElements()
+  }
+
+  // 将ele转换为data
+  toData (): Data {
+    let vertex = this.points.map(({ele, id}) => ({
+      id, x: numRetain(ele.x), y: numRetain(ele.y)
+    }))
+    let wall = this.lines.map(({ele, id}) => ({
+      id,
+      p1: this.getPointId(ele.points[0]),
+      p2: this.getPointId(ele.points[1])
+    }))
+    let column = this.columns.map(({ele}) => ({
+      line: this.getLineId(ele.attachment),
+      pos: [
+        numRetain(ele.points[0].x), numRetain(ele.points[0].y), 
+        numRetain(ele.points[1].x), numRetain(ele.points[1].y), 
+        numRetain(ele.points[3].x), numRetain(ele.points[3].y), 
+        numRetain(ele.points[2].x), numRetain(ele.points[2].y)
+      ]
+    }))
+    let window = this.cases.map(({ele}) =>({
+      line: this.getLineId(ele.attachment),
+      pos: [
+        numRetain(ele.linePoints[0].x), numRetain(ele.linePoints[0].y), 
+        numRetain(ele.linePoints[1].x), numRetain(ele.linePoints[1].y) 
+      ],
+      top: ele.top,
+      bottom: ele.bottom
+    }))
+    let door = this.doors.map(({ele}) =>({
+      line: this.getLineId(ele.attachment),
+      pos: [
+        numRetain(ele.linePoints[0].x), numRetain(ele.linePoints[0].y), 
+        numRetain(ele.linePoints[1].x), numRetain(ele.linePoints[1].y) 
+      ],
+      top: ele.top,
+      bottom: ele.bottom
+    }))
+
+    return {vertex, wall, window, door, column, room: this.data.room, surplus: this.data.surplus}
+  }
+
+  referElements() {
+    let eles = [
+      ...this.lines,
+      ...this.points,
+      ...this.cases,
+      ...this.doors,
+      ...this.columns
+    ]
+    eles.forEach(({ele}) => {
+      this.render.g.removeChild(ele.real)
+      this.render.elements.splice(this.render.elements.indexOf(ele), 1);
+    })
+    this.generateElements()
+  }
+
+  // 删除房间
+  delRoom(cground: Array<number>) {
+    let index = this.data.room.findIndex(({ground}) => cground === ground)
+    
+    if (~index) {
+      let [room] = this.data.room.splice(index, 1)
+      room.ground.forEach((p1Id, i) => {
+        if (i > room.ground.length - 2) return;
+        let p2Id = room.ground[i + 1]
+        let arr = [p1Id, p2Id]
+        let line = this.lines.find(({ele}) => {
+          let p1 = this.points.find(({ele: p}) => p === ele.points[0])
+          let p2 = this.points.find(({ele: p}) => p === ele.points[1])
+          return p1 && p2 && ~arr.indexOf(p1.id) && ~arr.indexOf(p2.id)
+        })
+        line && line.ele.destroy()
+      })
+      return true
+    } else {
+      return false
+    }
+  }
+
+  // 删除镂空区域
+  delHole(cground: Array<number>) {
+    let hIndex = -1;
+    let rindex = this.data.room.findIndex(({hole}) => {
+      hIndex = hole.findIndex(({pos}) => pos === cground)
+      return ~hIndex
+    })
+
+    if (~rindex) {
+      let [hole] = this.data.room[rindex].hole.splice(hIndex, 1)
+      hole.pos.forEach((p1Id, i) => {
+        if (i > hole.pos.length - 2) return;
+        let p2Id = hole.pos[i + 1]
+        let arr = [p1Id, p2Id]
+        let line = this.lines.find(({ele}) => {
+          let p1 = this.points.find(({ele: p}) => p === ele.points[0])
+          let p2 = this.points.find(({ele: p}) => p === ele.points[1])
+          return p1 && p2 && ~arr.indexOf(p1.id) && ~arr.indexOf(p2.id)
+        })
+        line && line.ele.destroy()
+      })
+      return true
+    } else {
+      return false      
+    }
+  }
+
+  // 构建所有ele
+  generateElements() {
+    let eles = [
+      ...this.lines.map(line => line.ele),
+      ...this.points.map(point => point.ele),
+      ...this.cases.map(c => c.ele),
+      ...this.doors.map(c => c.ele),
+      ...this.columns.map(c => c.ele)
+    ]
+    eles.forEach(this.generateElement)
+  }
+
+  attrs = [ 'cases', 'doors', 'columns', 'lines', 'points']
+  
+  // Element加装Destroy方法
+  retrofitElementDestroy (ele: CADElementTS) {
+    let destroy = ele.destroy
+    if (ele.__load_destroy) return;
+    ele.__load_destroy = true
+    ele.destroy = (...args) => {
+      // rooms字段中删掉
+      if (!(args as any)[1] && ele instanceof WallLine) {
+        
+
+        let p1 = this.points.find(pe => pe.ele === ele.points[0]).id
+        let p2 = this.points.find(pe => pe.ele === ele.points[1]).id
+            
+        this.data.room.forEach(room => {
+          if (~room.ground.indexOf(p1) && ~room.ground.indexOf(p2)) {
+            room.ground.splice(room.ground.indexOf(p1), 1)
+            // 如果一个房间或者镂空区域只剩两个点则销毁
+            if (room.ground.length <= 2) {
+              this.delHole(room.ground)
+            }
+          }
+
+          room.hole.forEach(hole => {
+            if (~hole.pos.indexOf(p1) && ~hole.pos.indexOf(p2)) {
+              hole.pos.splice(hole.pos.indexOf(p1), 1)
+
+              // 如果一个房间或者镂空区域只剩两个点则销毁
+              if (hole.pos.length <= 2) {
+                this.delHole(hole.pos)
+              }
+            } 
+          })
+        })
+
+        // let rooms = this.queryLineRoomPos(ele)
+        // for (let i = 0; i < rooms.length; i++) {
+        //   let {pos, arr} = rooms[i]
+        //   arr.splice(pos, 1)
+
+        //   // 如果一个房间或者镂空区域只剩两个点则销毁
+        //   if (arr.length <= 2) {
+        //     this.delHole(arr) || this.delRoom(arr)
+        //   }
+        // }
+      }
+
+      const rep = () => {
+        // 帮自身缓存的数组删除
+        this.attrs.forEach(attr => {
+          let index = this[attr].findIndex(({ele: e}) => e === ele)
+          if (~index) {
+            this[attr].splice(index, 1)
+            ele.__id = this[attr][index] && this[attr][index].id
+          }
+        })
+
+        this.render.remove(ele)
+      }
+
+      // 需要同步删除还是更新完属性后删除
+      if ((args as any)[0]) {
+        rep()
+      } else {
+        ele.nextTick(rep)
+      }
+      destroy.call(ele, ...args)
+      ele.__load_destroy = false
+    }
+  }
+  
+  // Element加装Destroy方法
+  retrofitElementIntercept (ele: WallLine) {
+    let intercept = ele.intercept
+    if (ele.__load_intercept) return;
+    ele.__load_intercept = true
+
+    ele.intercept = (...args) => {
+      let isPoints = Object.keys(args[1]).indexOf('points')
+
+      // 如果更改了points点要同步到rooms
+      if (~isPoints) {
+        let oldIds = ele.points.map(p => this.points.find(({ele}) => ele === p).id)
+        let rooms = this.queryLineRoomPos(ele)
+        ele.nextTick(() => {
+          let newIds = ele.points.map(p => this.points.find(({ele}) => ele === p).id)
+
+          rooms.forEach(({arr: ground}) => {
+            let oindex1 = ground.indexOf(oldIds[0])
+            let oindex2 = ground.indexOf(oldIds[1])
+
+            ~oindex1 && (ground[oindex1] = newIds[0])
+            ~oindex2 && (ground[oindex2] = newIds[1])
+          })
+        })
+      }
+      return intercept.apply(ele, args)
+    }
+  }
+
+  generateElement = (ele: CADElementTS) => {
+    this.render.push(ele)
+    this.retrofitElementDestroy(ele)
+    ele instanceof WallLine && 
+      this.retrofitElementIntercept(ele)
+  }
+
+  // 查找线段第一个点在rooms中的位置
+  queryLineRoomPos (line: WallLine) {
+    let rooms = this.data.room
+    let p1 = this.points.find(pe => pe.ele === line.points[0])
+    let p2 = this.points.find(pe => pe.ele === line.points[1])
+    let hs = []
+
+    rooms = rooms.filter((room, i) => {
+      let i1 = room.ground.indexOf(p1.id)
+      let i2 = room.ground.indexOf(p2.id)
+      let j = Math.abs(i1 - i2)
+      return ~i1 && ~i2 && (j === 1 || j === room.ground.length - 1)
+    })
+
+    if (!rooms.length || !p1) return hs
+    let pointId = line.points[0].__id || p1.id
+
+    if (line.isOut) {
+      for (let i = 0; i < rooms.length; i++) {
+        let index, hole = rooms[i].hole.find(({pos: h}) => ~(index = h.indexOf(pointId)))
+        if (hole) hs.push({ pos: index, arr: hole.pos})
+      }
+    } else {
+      for (let i = 0; i < rooms.length; i++) {
+        let index = rooms[i].ground.indexOf(pointId)
+        if (~index) hs.push({ pos: index, arr: rooms[i].ground })
+      }
+    }
+
+    return hs
+  }
+  destroy() {
+    let elesArr = this.attrs.map(attr => this[attr])
+    this.data = {
+      vertex: [],
+      wall: [],
+      room: [],
+      window: [],
+      column: [],
+      door: [],
+      surplus: []
+    }
+    elesArr.forEach(eles => {
+      while (eles.length) {
+        eles[0].ele.destroy(true)
+      }
+    })
+    this.render.destroy()
+    this.render = null
+  }
+}
+
+export default Processing

+ 203 - 0
src/CAD/core/base/processing/index.ts

@@ -0,0 +1,203 @@
+import Render from '../renderer'
+import Point from '../../core/point'
+import WallLine from '../../core/wallline'
+import Casement from '../../architecture/casement'
+import Door from '../../architecture/door/index'
+import Column from '../../architecture/column'
+import FurnColumn from '../../furniture/column'
+import FurnFlue from '../../furniture/flue'
+import { CADElementTS } from '../../core/element'
+import Tagging from '../../label/tagging'
+import SlideDoor from '../../architecture/slideDoor'
+import GroundCase from '../../architecture/groundCase'
+import { methods as toEleMethods, ToEle } from './toEle'
+import { methods as roomMethods, RoomApi } from './room'
+import { methods as dataMethods, dataApi } from './toData'
+import { methods as pointOperMethods, PointOper } from './pointOper'
+import BayCase from '../../architecture/bayCase'
+import {AttachCAD} from '../../index'
+
+
+export interface _Point { x: number, y:number, id: number }
+export interface _Line { p1: number, p2: number, id: number, border: boolean, exterior?: boolean }
+export interface _window { pos: Array<number>, line: number  }
+export interface _door { pos: Array<number>, line: number, within: number, show?: boolean, ctl?: Array<number>, start: boolean }
+export interface _slideDoor { pos: Array<number>, line: number, within: number }
+export interface _groundCase { pos: Array<number>, line: number }
+export interface _bayCase { pos: Array<number>, line: number, within: number, ctl?: Array<number> }
+export interface _column { pos: Array<number>, line: number }
+export interface _furnColumn { pos: Array<number>,  angle: number }
+export interface _furnFlue { pos: Array<number>,  angle: number }
+export interface _tagging { pos: Array<number>, title: string, content: string, showTitle: boolean, showContent: boolean}
+export interface _hole {pos: Array<number>, top: number}
+export type _Ground = Array<number>
+export interface _room { id: number, ground: _Ground, hole: Array<_hole>, close: boolean }
+export interface Data {
+  room?: Array<{
+    wall: Array<number>,
+    ground: Array<number>
+  }>,
+  vertex: Array<_Point>,
+  surplus: Array<_Point>,
+  wall: Array<_Line>,
+  window: Array<_window>,
+  door: Array<_door>,
+  column: Array<_column>,
+  furnColumn: Array<_furnColumn>,
+  furnFlue: Array<_furnFlue>,
+  slideDoor: Array<_slideDoor>
+  groundCase: Array<_groundCase>,
+  bayCase: Array<_bayCase>,
+  tagging: Array<_tagging>,
+  dire: number
+}
+export interface StorageProps{ dom: HTMLElement }
+export interface CADState {elements: Array<Point>}
+export interface rhole extends _hole {bottom: number} 
+export type Walls = Array<{ id: number; ele: WallLine, border: boolean, exterior?: boolean, isOut?: boolean }>
+export type Points = Array<{ id: number; ele: Point }>
+export type Cases = Array<{ele: Casement}>
+export type Doors = Array<{ele: Door}>
+export type SlideDoors = Array<{ele: SlideDoor}>
+export type GroundCases = Array<{ele: GroundCase}>
+export type Columns = Array<{ele: Column}>
+export type FurnColumns = Array<{ele: FurnColumn}>
+export type FurnFlues = Array<{ele: FurnFlue}>
+export type Taggings = Array<{ele: Tagging}>
+export type BayCases = Array<{ele: BayCase}>
+
+export class _Processing {
+  data: Data
+  render: Render
+  points: Points
+  lines: Walls
+  cases: Cases
+  doors: Doors
+  slideDoors: SlideDoors
+  groundCases: GroundCases
+  bayCases: BayCases
+  columns: Columns
+  taggings: Taggings
+  insertModel: boolean
+  furnColumns: FurnColumns
+  furnFlues: FurnFlues
+  cad: AttachCAD
+  
+  constructor({dom}: StorageProps) {
+    this.render = new Render({ layer: dom, processing: (this as unknown as ProcessingTS) })
+    this.points = []
+    this.lines = []
+    this.cases = []
+    this.doors = []
+    this.columns = []
+    this.slideDoors = []
+    this.groundCases = []
+    this.taggings = []
+    this.bayCases = []
+    this.furnColumns = []
+    this.furnFlues = []
+  }
+
+  // 删除元素
+  rep(this: ProcessingTS, ele: CADElementTS) {
+    this.attrs.forEach(attr => {
+      let index = this[attr].findIndex(({ele: e}) => e === ele)
+
+      if (~index) {
+        ele.__id = this[attr][index] && this[attr][index].id
+        this[attr].splice(index, 1)
+      }
+    })
+    this.render.remove(ele)
+  }
+
+  // Element加装Destroy方法
+  retrofitElementDestroy (this: ProcessingTS,ele: CADElementTS) {
+    let destroy = ele.destroy
+    if (ele.__load_destroy) return;
+    ele.__load_destroy = true
+    ele.destroy = (...args) => {
+      // 需要同步删除还是更新完属性后删除
+      if ((args as any)[0]) {
+        this.rep(ele)
+      } else {
+        ele.nextTick(() => this.rep(ele))
+      }
+      destroy.call(ele, ...args)
+      ele.__load_destroy = false
+    }
+  }
+
+  updateWallPoints(this: ProcessingTS, ele: WallLine, oldPoints: [Point, Point], newPoints: [Point, Point]) {
+    
+  }
+  
+  // Element加装Destroy方法
+  retrofitElementIntercept (this: ProcessingTS, ele: WallLine) {
+    return;
+    
+  }
+
+  depLines() {
+    let lines = [...this.lines]
+    for (let i = 0; i< lines.length; i++) {
+      let line = lines[i].ele
+      let ret = lines.some(({ele: eline}) => 
+        line !== eline && (
+          (line.points[0] === eline.points[0] && line.points[1] === eline.points[1]) ||
+          (line.points[1] === eline.points[0] && line.points[0] === eline.points[1])
+        )
+      )
+
+      if (line.points[0] === line.points[1] || ret) {
+        lines[i].ele.destroy()
+        lines.splice(i--, 1)
+      }
+    }
+  }
+
+
+  destroy(this: ProcessingTS) {
+    let elesArr = this.attrs.map(attr => this[attr])
+    this.data = {
+      vertex: [],
+      wall: [],
+      window: [],
+      column: [],
+      door: [],
+      surplus: [],
+      slideDoor: [],
+      tagging: [],
+      groundCase: [],
+      bayCase: [],
+      furnColumn: [],
+      furnFlue: [],
+      dire: 0
+    }
+    elesArr.forEach(eles => {
+      while (eles.length)
+        eles[0].ele.destroy(true)
+    })
+    this.render.destroy()
+    this.render = null
+  }
+}
+
+export type ProcessingTS = _Processing & ToEle & RoomApi & dataApi & PointOper
+export default _Processing as unknown as {
+  new (data: StorageProps): ProcessingTS
+}
+export type That = {
+  [k in any]: ((this: ProcessingTS, ...args) => any)
+}
+
+const apis = {
+  ...toEleMethods,
+  ...roomMethods,
+  ...dataMethods,
+  ...pointOperMethods
+}
+Object.keys(apis).forEach(
+  k => _Processing.prototype[k] = apis[k]
+)
+

+ 76 - 0
src/CAD/core/base/processing/pointOper.ts

@@ -0,0 +1,76 @@
+import LinePoint from "../../core/linepoint";
+import Point from "../../core/point";
+import LineArch from '../../architecture/linearch'
+import { That } from './index'
+import { isContainPoint, lineDis, pointLineDis } from "../../geometry";
+import WallLine from "../../core/wallline";
+import { SEFTLINE } from "../../constant/Element";
+
+
+export interface PointOper {
+  lineInsertPoint: (line: WallLine, point: Point) => { lines: Array<WallLine>, promis: Promise<any>, ret: boolean }
+}
+
+export const methods: That = {
+  
+  // 点内插入新点,所需要的操作 
+  lineInsertPoint(aline: WallLine, operPoint: Point) {
+    let promises = []
+    let lines = []
+    let processing = this
+    // 获取进入点的id等配置
+    let proPoint = processing.points.find(p => p.ele === operPoint)
+
+    if (!proPoint) return {ret: false}
+
+    processing.lines.forEach(({ele: line, border}) => {
+      if (!( ~line.points.indexOf(aline.points[0]) && ~line.points.indexOf(aline.points[1] ))) return;
+      if (processing.lines.some(({ele: cline}) => ~cline.points.indexOf(operPoint) && ~cline.points.indexOf(line.points[0]))) return 
+
+    
+      // let joinLines = processing.lines.filter(cline => ~cline.ele.points.indexOf(aline.points[0]) && ~cline.ele.points.indexOf(aline.points[1]))
+      let archs = LineArch.attaArch.get(line)
+      // joinLines.forEach(line => archs = archs.concat(LineArch.attaArch.get(line.ele) || []))
+      
+
+      this.cad.increase
+      
+      let proLine = {ele: this.cad.increase(SEFTLINE, {
+        id: this.getNewLineId(),
+        p1: processing.getPointId(line.points[0]),
+        p2: proPoint.id,
+        border: line.border,
+        exterior: line.exterior, 
+        isOut: line.isOut
+      }).obj }
+      
+      lines.push(proLine)
+
+      proLine.ele.update()
+      
+      let points = [proPoint.ele, line.points[1]] as [LinePoint, LinePoint]
+      line.points = points
+      line.listenPointDrag([proPoint.ele, line.points[1]])
+      promises.push(
+        new Promise(r => {
+          line.nextTick(() => {
+            line.updateJoin()
+            proLine.ele.updateJoin()
+            // 划分建筑附属线条分配
+            archs && [...archs].forEach(arch => {
+              if (pointLineDis(proLine.ele, arch.linePoints[0]) < 0.1 && pointLineDis(proLine.ele, arch.linePoints[1]) < 0.1) {
+                arch.setAttachment(proLine.ele)
+              } else {
+                console.log('----------')
+              }
+
+            })
+            setTimeout(r, 200)
+          })
+        })
+      )
+    })
+    
+    return { lines, promis: Promise.all(promises), ret: true }
+  }
+}

+ 35 - 0
src/CAD/core/base/processing/room.ts

@@ -0,0 +1,35 @@
+import { That, _hole, _room, rhole } from './index'
+import WallLine from '../../core/wallline'
+import Point from '../../core/point'
+
+
+export interface RoomApi {
+  getJoinRooms: (room: _room) => Array<_room>
+  getPointsByRoom: (room: _room) => Array<Point>
+  getPointsByHole: (rhole: _hole) => Array<Point>
+  getLinesByRoom: (room: _room) => Array<WallLine>
+  getLinesByHole: (rhole: _hole) => Array<WallLine>
+  getRoomsByPoint: (point: Point) => Array<_room>
+  getHolesByPoint: (point: Point) => Array<rhole>
+  getRoomsByLine: (line: WallLine) => Array<_room>
+  getHolesByLine: (line: WallLine) => Array<rhole>
+  queryLineRoomPos: (line: WallLine) => Array<{ pos: number, arr: Array<number>}>
+  delRoom: (cground: Array<number>) => boolean
+  delHole: (cground: Array<number>) => boolean
+  getRoomEles: (room: _room) => { ground: Array<Point>, hole: Array<Array<Point>>}
+}
+
+
+export const methods: That = {
+  
+  getRoomEles(room: _room) {
+    return {
+      ground: room.ground.map(
+        pid => this.points.find(({id}) => id === pid).ele
+      ),
+      hole: room.hole.map(
+        ({pos}) => pos.map(pid => this.points.find(({id}) => id === pid).ele )
+      )
+    }
+  }
+}

+ 187 - 0
src/CAD/core/base/processing/toData.ts

@@ -0,0 +1,187 @@
+import Point from '../../core/point'
+import WallLine from '../../core/wallline'
+import {LineArchClass as LineArch} from '../../architecture/linearch'
+import { _Point, _Line, _window, _door, _column, Data, That, _tagging, _groundCase, _bayCase, _furnColumn, _furnFlue, _slideDoor } from './index'
+import Door from '../../architecture/door/index'
+import Casement from '../../architecture/casement'
+import Column from '../../architecture/column'
+import Tagging from '../../label/tagging'
+import GroundCase from '../../architecture/groundCase'
+import BayCase from '../../architecture/bayCase'
+import FurnColumn from '../../furniture/column'
+import FurnFlue from '../../furniture/flue'
+import { FurnClass as Furn } from '../../furniture/furn'
+import SlideDoor from '../../architecture/slideDoor'
+
+export interface dataApi {
+  getNewPointId: () => number
+  getNewLineId: () => number
+  getLineId: (line: WallLine) => number
+  getPointId: (point: Point) => number
+  baseArchToData: (ele: LineArch) => _door
+  roomArchToData: (ele: Furn) => _furnColumn
+  pointToData: (ele: {ele: Point, id: number}) => _Point
+  lineToData: (ele: {ele: WallLine, id: number}) => _Line
+  doorToData: (ele: {ele: Door}) => _door
+  slideDoorToData: (ele: {ele: Door}) => _slideDoor
+  caseToData: (ele: {ele: Casement}) => _window
+  groundCaseToData: (ele: {ele: GroundCase}) => _groundCase
+  BayCaseToData: (ele: {ele: BayCase}) => _bayCase
+  columnToData: (ele: {ele: Column}) => _column
+  taggingToData: ({ele}: {ele: Tagging}) => _tagging,
+  furnColumnToData: (ele: {ele: FurnColumn}) => _furnColumn,
+  furnFlueToData: (ele: {ele: FurnFlue}) => _furnFlue
+  toData: () => Data
+}
+
+const numRetain = (n: number, m = 2) => Number(n.toFixed(m))
+
+export const methods: That = {
+  baseArchToData(ele: LineArch) {
+    return {
+      line: this.getLineId(ele.attachment),
+      pos: [
+        numRetain(ele.linePoints[0].x), numRetain(ele.linePoints[0].y), 
+        numRetain(ele.linePoints[1].x), numRetain(ele.linePoints[1].y) 
+      ],
+      top: ele.top,
+      bottom: ele.bottom
+    }
+  },
+  roomArchToData(ele: FurnColumn) {
+    return {
+      pos: [
+        numRetain(ele.points[0].x), numRetain(ele.points[0].y), 
+        numRetain(ele.points[1].x), numRetain(ele.points[1].y),
+        numRetain(ele.points[2].x), numRetain(ele.points[2].y), 
+        numRetain(ele.points[3].x), numRetain(ele.points[3].y) 
+      ],
+      top: ele.top,
+      bottom: ele.bottom,
+      angle: ele.angle
+    }
+  },
+  pointToData({ele, id}) {
+    return {
+      id,
+      x: numRetain(ele.x), y: numRetain(ele.y)
+    }
+  },
+  lineToData({ele, id, border}) {
+    return {
+      id,
+      p1: this.getPointId(ele.points[0]),
+      p2: this.getPointId(ele.points[1]),
+      border,
+      exterior: ele.exterior
+    }
+  },
+  doorToData ({ele}: {ele: Door}) {
+    let data = this.baseArchToData(ele)
+    data.within = ele.within
+    data.show = ele.show
+    data.ctl = [ele.ctl[0].x, -ele.ctl[0].y]
+    data.start = ele.start
+    return data
+  },
+  slideDoorToData ({ele}: {ele: SlideDoor}) {
+    let data = this.baseArchToData(ele)
+    data.within = ele.within
+    data.show = ele.show
+    return data
+  },
+  caseToData ({ele}: {ele: Casement}) {
+    return this.baseArchToData(ele)
+  },
+  groundCaseToData ({ele}: {ele: GroundCase}) {
+    return this.baseArchToData(ele)
+  },
+  BayCaseToData ({ele}: {ele: BayCase}) {
+    let data = this.baseArchToData(ele)
+    data.within = ele.within
+    data.ctl = [ele.ctl[0].x, -ele.ctl[0].y, ele.ctl[1].x, -ele.ctl[1].y]
+    return data
+  },
+  columnToData ({ele}: {ele:Column}) {
+    let data = this.baseArchToData(ele)
+    return {
+      ...data,
+      pos: [
+        ...data.pos,
+        numRetain(ele.points[3].x), numRetain(ele.points[3].y),
+        numRetain(ele.points[2].x), numRetain(ele.points[2].y)
+      ]
+    }
+  },
+  taggingToData ({ele}: {ele: Tagging}) {
+    return {
+      pos: [ele.point.x, ele.point.y],
+      title: ele.title,
+      content: ele.content,
+      showTitle: ele.showTitle,
+      showContent: ele.showContent
+    }
+  },
+  furnColumnToData ({ele}: {ele: FurnColumn}) {
+    return this.roomArchToData(ele)
+  },
+  furnFlueToData ({ele}: {ele: FurnFlue}) {
+    return this.roomArchToData(ele)
+  },
+  
+  getNewPointId() {
+    if (this.points.length === 0) {
+      return 1
+    } else {
+      return Math.max(...this.points.map(({id}) => id)) + 1
+    }
+  },
+  getNewLineId() {
+    if (this.lines.length === 0) {
+      return 1
+    } else {
+      return Math.max(...this.lines.map(({id}) => id)) + 1
+    }
+  },
+  getLineId (line: WallLine) {
+    let l = this.lines.find(({ele}) => ele === line)
+    return l ? l.id : 0
+  },
+  getPointId (point: Point) {
+    return this.points.find(({ele}) => ele === point).id
+  },
+  // 将ele转换为data
+  toData (): Data {
+    let vertex = this.points.map(a => this.pointToData(a))
+
+    let column = this.columns.map(a => this.columnToData(a))
+    let window = this.cases.map(a => this.caseToData(a))
+    let door = this.doors.map(a => this.doorToData(a))
+    let slideDoor = this.slideDoors.map(a => this.slideDoorToData(a))
+    let groundCase = this.groundCases.map(a => this.groundCaseToData(a))
+    let tagging = this.taggings.map(a => this.taggingToData(a))
+    let bayCase = this.bayCases.map(a => this.BayCaseToData(a))
+
+
+
+    let furnColumn = this.furnColumns.map(a => this.furnColumnToData(a))
+    let furnFlue = this.furnFlues.map(a => this.furnFlueToData(a))
+    let wall = []
+    this.lines.forEach(a => {
+      try {
+        wall.push(this.lineToData(a))
+      } catch {
+      }
+    })
+
+
+    return {
+      vertex, wall, 
+      window, door, column, 
+      surplus: this.data.surplus, 
+      slideDoor, groundCase, tagging, 
+      bayCase, furnColumn, furnFlue,
+      dire: this.data.dire
+    }
+  }
+}

+ 349 - 0
src/CAD/core/base/processing/toEle.ts

@@ -0,0 +1,349 @@
+import Point from '../../core/point'
+import WallLine from '../../core/wallline'
+import Casement from '../../architecture/casement'
+import Door from '../../architecture/door/index'
+import SlideDoor from '../../architecture/slideDoor'
+import Column from '../../architecture/column'
+import FurnColumn from '../../furniture/column'
+import FurnFlue from '../../furniture/flue'
+import Tagging from '../../label/tagging'
+import { CADElementTS } from '../../core/element'
+import { _Point, _Line, _window, _door, _column, Data, That, _tagging, _groundCase, _slideDoor, _bayCase, _room, _hole, _furnColumn, _furnFlue } from './index'
+import GroundCase from '../../architecture/groundCase'
+import BayCase from '../../architecture/bayCase'
+import Line from '../../core/wallline'
+import {Point as GeoPoint, pointInside} from '../../geometry'
+
+export interface ToEle {
+  addPoint: ({id, x, y}: _Point) => { id: number; ele: Point },
+  addLine: ({id, p1, p2, exterior, isOut}: _Line &{isOut: boolean}) => { id: number; ele: WallLine },
+  addCase: ({pos, line}: _window) => {ele: Casement},
+  addDoor: ({pos, line, start}: _door) => {ele: Door},
+  addSlideDoor: ({pos, line, within}: _slideDoor) => {ele: SlideDoor},
+  addGroundCase: ({pos, line}: _groundCase) => {ele: SlideDoor},
+  addBayCase: ({pos, line}: _bayCase) => {ele: BayCase},
+  addColumn: ({pos, line}: _column) => {ele: Column},
+  addRoom: ({points}: {points: Array<GeoPoint>}) => {ele: Line},
+  addFurnColumn: ({pos}: _furnColumn) => {ele: FurnColumn},
+  addFurnFlue: ({pos}: _furnFlue) => {ele: FurnFlue},
+  addTagging: ({pos, title, content}: _tagging) => {ele: Column},
+  generateElement: (ele: CADElementTS) => void
+  generateElements: () => void
+  referElements: () => void
+  toEles: (data: Data) => void
+  attrs: Array<string>
+}
+
+
+export const methods: That = {
+  addPoint({id, x, y}) {
+    let ret = {id, ele: new Point({x, y, renderer: this.render})}
+    this.points.push(ret)
+    this.generateElement(ret.ele)
+    return ret
+  },
+
+  addLine ({id, p1, p2, border, exterior, isOut}) {
+    // const rooms = this.data.room.filter(({hole}) => hole.some(({pos: h}) => ~h.indexOf(p1) && ~h.indexOf(p2)))
+    // const isOut = !!rooms.length
+
+  
+    let ret = {
+      id, 
+      ele: new WallLine({
+        border,
+        exterior,
+        points: [
+          this.points.find(p => p.id === p1).ele, 
+          this.points.find(p => p.id === p2).ele
+        ],
+        renderer: this.render,
+        // color: exterior ? 'red': void 0,
+        isOut
+      }),
+      border,
+      isOut,
+      exterior
+    }
+    this.lines.push(ret)
+    this.generateElement(ret.ele)
+
+    if (exterior) {
+      [ret.ele, ...ret.ele.points].forEach(point => point.real.setAttribute('pointer-events', 'none'))
+    }
+
+    return ret
+  },
+
+  addCase({pos, line, top = null, bottom = null}) {
+    let ret = {
+      ele: new Casement({
+        renderer: this.render,
+        attachment: this.lines.find(({id}) => id === line).ele,
+        points: [{x: pos[0], y: pos[1]}, {x: pos[2], y: pos[3]}],
+        top,
+        bottom
+      })
+    }
+    this.cases.push(ret)
+    this.generateElement(ret.ele)
+    return ret
+  },
+
+  addDoor ({pos, line, top = null, bottom = null, within, show, start}) {
+    let ret = {
+      ele: new Door({
+        show,
+        renderer: this.render,
+        attachment: this.lines.find(({id}) => id === line).ele,
+        points: [{x: pos[0], y: pos[1]}, {x: pos[2], y: pos[3]}],
+        top, bottom,
+        within: within,
+        start
+      })
+    }
+    this.doors.push(ret)
+    this.generateElement(ret.ele)
+    return ret
+  },
+
+  addSlideDoor ({pos, line, top = null, bottom = null, within = 0}) {
+    let ret = {
+      ele: new SlideDoor({
+        within: within,
+        bwithin: 1.5,
+        renderer: this.render,
+        attachment: this.lines.find(({id}) => id === line).ele,
+        points: [{x: pos[0], y: pos[1]}, {x: pos[2], y: pos[3]}],
+        top, bottom
+      })
+    }
+    this.slideDoors.push(ret)
+    this.generateElement(ret.ele)
+    return ret
+  },
+  
+  addBayCase ({pos, line, top = null, bottom = null, within}) {
+    let ret = {
+      ele: new BayCase({
+        renderer: this.render,
+        attachment: this.lines.find(({id}) => id === line).ele,
+        points: [{x: pos[0], y: pos[1]}, {x: pos[2], y: pos[3]}],
+        top, bottom,
+        within
+      })
+    }
+    this.bayCases.push(ret)
+    this.generateElement(ret.ele)
+    return ret
+  },
+
+  addGroundCase ({pos, line, top = null, bottom = null}) {
+    let ret = {
+      ele: new GroundCase({
+        renderer: this.render,
+        attachment: this.lines.find(({id}) => id === line).ele,
+        points: [{x: pos[0], y: pos[1]}, {x: pos[2], y: pos[3]}],
+        top, bottom
+      })
+    }
+    this.groundCases.push(ret)
+    this.generateElement(ret.ele)
+    return ret
+  },
+
+  addColumn ({pos, line}: _column) {
+    let ret = {
+      ele: new Column({
+        renderer: this.render,
+        attachment: this.lines.find(({ id }) => id === line).ele,
+        points: [
+          {x: pos[0], y: pos[1]},
+          {x: pos[2], y: pos[3]},  
+          {x: pos[6], y: pos[7]},
+          {x: pos[4], y: pos[5]}
+        ]
+      })
+    }
+    this.columns.push(ret)
+    this.generateElement(ret.ele)
+    return ret
+  },
+
+  addFurnColumn ({pos, angle}: _furnColumn) {
+    let ret = {
+      ele: new FurnColumn({
+        points: [
+          {x: pos[0], y: pos[1]},
+          {x: pos[2], y: pos[3]},  
+          {x: pos[4], y: pos[5]},
+          {x: pos[6], y: pos[7]}
+        ], 
+        renderer: this.render,  angle
+      })
+    }
+    this.furnColumns.push(ret)
+    this.generateElement(ret.ele)
+    return ret
+  },
+  
+  addRoom ({points}: {points: Array<GeoPoint>}) {
+    const tpoints = points.map(point => 
+      this.addPoint({
+        ...point,
+        id: this.getNewPointId()
+      })
+    )
+
+    const tline = []
+    for (let i = 0; i < tpoints.length - 1; i++) {
+      tline.push(
+        this.addLine({
+          id: this.getNewLineId(),
+          p1: tpoints[i].id,
+          p2: tpoints[i + 1].id,
+          isOut: false,
+          border: false
+        })
+      )
+    }
+
+    tline.forEach(line => line.ele.update())
+
+    this.data.vertex.push(
+      ...tpoints.map(tp => ({
+        id: tp.id,
+        x: tp.ele.x,
+        y: tp.ele.y
+      }))
+    )
+    this.data.wall.push(
+      ...tline.map(tp => tp.id)
+    )
+    return tline
+  },
+
+  addFurnFlue ({pos, angle}: _furnFlue) {
+    let ret = {
+      ele: new FurnFlue({
+        points: [
+          {x: pos[0], y: pos[1]},
+          {x: pos[2], y: pos[3]},  
+          {x: pos[4], y: pos[5]},
+          {x: pos[6], y: pos[7]}
+        ], 
+        renderer: this.render, angle
+      })
+    }
+    this.furnFlues.push(ret)
+    this.generateElement(ret.ele)
+    return ret
+  },
+
+
+  addTagging ({pos, title, content, show = true, showTitle, showContent}) {
+    let ret = {
+      ele: new Tagging({
+        show,
+        pos: {x: pos[0], y: pos[1]}, 
+        title, content, 
+        renderer: this.render,
+        showTitle: showTitle,
+        showContent: showContent
+      })
+    }
+
+    this.taggings.push(ret)
+    this.generateElement(ret.ele)
+
+    return ret
+  },
+
+  generateElement(ele: CADElementTS) {
+    this.render.push(ele)
+    this.retrofitElementDestroy(ele)
+    ele instanceof WallLine && 
+      this.retrofitElementIntercept(ele)
+  },
+  
+  // 构建所有ele
+  generateElements() {
+    let eles = [
+      ...this.lines.map(line => line.ele),
+      ...this.points.map(point => point.ele),
+      ...this.cases.map(c => c.ele),
+      ...this.doors.map(c => c.ele),
+      ...this.columns.map(c => c.ele)
+    ]
+    eles.forEach(a => this.generateElement(a))
+  },
+  
+  
+  // 将数据转换为ele
+  toEles (data: Data) {
+    let {vertex, wall, window, door, column, slideDoor, tagging, groundCase, bayCase, furnColumn, furnFlue} = data
+    let count = 0
+    this.data = data
+
+    vertex.forEach(a => this.addPoint(a))
+    wall.forEach(({p1, p2, border, exterior}) => {
+      this.addLine({p1, p2, id: ++count, border: border, exterior: exterior, isOut: false})
+    });
+
+    [window, door, column, slideDoor, groundCase, bayCase].forEach(objs => {
+      objs.forEach(obj => {
+        let aline = wall.find(({id}) => obj.line === id)
+        if (!aline) {
+          obj.line = 0
+          return;
+        }
+        let {p1, p2} = aline
+        let line = this.lines.find(({id, ele}) => {
+          let ids = ele.points.map(cpoint => this.points.find(({ele: point}) => point === cpoint).id)
+          return ~ids.indexOf(p1) && ~ids.indexOf(p2)
+        })
+        obj.line = line.id
+      })
+    })
+
+
+
+    
+    window.forEach(a => a.line !== 0 && this.addCase(a))
+    door.forEach(a => a.line !== 0 && this.addDoor(a))
+    column.forEach(a => a.line !== 0 && this.addColumn(a))
+    slideDoor.forEach(a => a.line !== 0 && this.addSlideDoor(a))
+    groundCase.forEach(a => a.line !== 0 && this.addGroundCase(a))
+    bayCase.forEach(a => a.line !== 0 && this.addBayCase(a))
+    tagging.forEach(a => this.addTagging(a))
+
+    
+    furnColumn.forEach(a => {
+      this.addFurnColumn(a)
+    })
+    furnFlue.forEach(a => {
+      this.addFurnFlue(a)
+    })
+    
+
+    this.referElements()
+  },
+
+  referElements() {
+    let eles = [
+      ...this.lines,
+      ...this.points,
+      ...this.cases,
+      ...this.doors,
+      ...this.columns
+    ]
+    eles.forEach(({ele}) => {
+      this.render.g.removeChild(ele.real)
+      this.render.elements.splice(this.render.elements.indexOf(ele), 1);
+    })
+    this.generateElements()
+  }
+};
+
+
+(methods as any).attrs = [ 'cases', 'doors', 'slideDoors', 'columns', 'lines', 'points', 'groundCases', 'taggings', 'bayCases', 'furnColumns', 'furnFlues']

+ 155 - 0
src/CAD/core/base/renderer.ts

@@ -0,0 +1,155 @@
+import {CADElementTS, CADElement} from '../core/element'
+import {SVGURI} from '../constant/Element'
+import {ProcessingTS} from './processing/index'
+
+export type Screen = {x: number, y: number}
+
+interface RendererProps {
+  layer: HTMLElement,
+  width?: number,
+  height?: number,
+  processing: ProcessingTS
+}
+
+class Renderer {
+  edit: boolean
+  svg: SVGSVGElement
+  g: SVGGElement
+  layer: HTMLElement
+  elements: Array<CADElementTS>
+  processing: ProcessingTS
+  props: {
+    left: number;
+    top: number;
+    width: number;
+    height: number,
+    multiple: number,
+    scale: number
+  }
+  realWidth: number
+  realHeight: number
+
+  constructor({layer, width = layer.offsetWidth, height = layer.offsetHeight, processing}: RendererProps) {
+    CADElement.init(this)
+
+    this.props = {left: 0, top: 0, width, height, multiple: 1, scale: 1}
+    this.elements = []
+    this.processing = processing
+    this.init(layer)
+  }
+
+  private clickHandle = () => {
+    this.elements.forEach(e => e.changeSelect(false))
+  }
+  
+  private init(layer) {
+    this.svg = document.createElementNS(SVGURI, 'svg')
+    this.g = document.createElementNS(SVGURI, 'g')
+    this.svg.appendChild(this.g)
+    this.layer = layer
+
+    this.layer.style.position = 'relative'
+    this.svg.style.position = 'absolute'
+    this.svg.style.left = '0'
+    this.svg.style.top = '0'
+    this.svg.style.right = '0'
+    this.svg.style.bottom = '0'
+    this.svg.setAttribute('version', '1.0')
+    this.svg.setAttribute('xmlns', 'http://www.w3.org/2000/svg')
+    this.svg.setAttribute('xmlns:xlink', 'http://www.w3.org/1999/xlink')
+    this.svg.addEventListener('click', this.clickHandle, false)
+    this.layer.appendChild(this.svg)
+  }
+
+  adaptLayer(width = this.layer.offsetWidth, height = this.layer.offsetHeight) {
+    
+    let centerStepX = this.props.width / 2 - this.props.left
+    let centerStepY = this.props.height / 2 - this.props.top
+
+    this.g.setAttribute('transform', `
+      translate(${this.props.left},${this.props.top})
+      translate(${centerStepX},${centerStepY})
+      scale(${this.props.scale},${this.props.scale})
+      translate(${-centerStepX},${-centerStepY})
+    `)
+    this.svg.setAttribute('width', width.toString())
+    this.svg.setAttribute('height', height.toString())
+    this.svg.setAttribute('viewBox', `0 0 ${this.props.width} ${this.props.height}`)
+    this.realWidth = width
+    this.realHeight = height
+
+    CADElement.update(this)
+  }
+
+  render() {
+    CADElement.update(this)
+  }
+  
+  // dom坐标转换为真实坐标
+  screenToRealPoint({x, y}: Screen) {
+    let centerStepX = this.props.width / 2 - this.props.left
+    let centerStepY = this.props.height / 2 - this.props.top
+    let width = this.props.width / this.props.multiple
+    let height = this.props.height / this.props.multiple
+    if(width == 0 || height == 0){
+      return{
+        x:centerStepX,
+        y:centerStepY
+      };
+    }
+    else{
+      return {
+        x: ((x * this.props.width) / width - this.props.left - centerStepX)  / this.props.scale + centerStepX,
+        y: ((y * this.props.height) / height - this.props.top - centerStepY) / this.props.scale + centerStepY
+      }
+    }
+  }
+
+  // 点坐标转换真实坐标
+  realPointToScreen({x, y}: Screen) {
+    let centerStepX = this.props.width / 2 - this.props.left
+    let centerStepY = this.props.height / 2 - this.props.top
+    let width = this.props.width / this.props.multiple
+    let height = this.props.height / this.props.multiple
+
+    return {
+      x: ((x - centerStepX) * this.props.scale + centerStepX + this.props.left) * width / this.props.width,
+      y: ((y - centerStepY) * this.props.scale + centerStepY + this.props.top) * height / this.props.height
+    }
+  }
+
+  push (...elements: Array<CADElementTS>) {
+    elements.forEach(element => {
+      let prevEle = this.elements.find(ele => ele.zIndex > element.zIndex)
+      
+      if (prevEle) {
+        try {
+          this.g.insertBefore(element.real, prevEle.real)
+        } catch(e) {
+          this.g.appendChild(element.real)  
+        }
+        this.elements.splice(this.elements.indexOf(prevEle), 0, element)
+      } else {
+        this.g.appendChild(element.real)
+        this.elements.push(element)
+      }
+    })
+  }
+
+  remove (...elements: Array<CADElementTS>) {
+    elements.forEach(element => {
+      try {
+        this.g.removeChild(element.real)
+        this.elements.splice(this.elements.indexOf(element), 1)
+      } catch {
+      }
+    })
+  }
+
+  destroy() {
+    this.svg.removeEventListener('click', this.clickHandle, false)
+    this.layer.removeChild(this.svg)
+  }
+}
+
+export default Renderer

+ 64 - 0
src/CAD/core/base/stack.ts

@@ -0,0 +1,64 @@
+
+class Stack {
+  private length: string
+  private index: string
+  private name: string
+  private keys: Array<string>
+
+  constructor (name: string) {
+    this.length = name + '__length'
+    this.index = name + '__index'
+    this.name = name
+    this.keys = []
+    this.init()
+  }
+
+  init() {
+    sessionStorage.setItem(this.length, '0')
+  }
+
+  getKey(index) {
+    return this.name + '__' + index
+  }
+
+  getData () {
+    return sessionStorage.getItem(this.index)
+  }
+
+  getLength () {
+    return Number(sessionStorage.getItem(this.length))
+  }
+
+  setLength (i) {
+    sessionStorage.setItem(this.length, i)
+  }
+
+  push(data) {
+    let length = this.getLength()
+    let key = this.getKey(this.getLength())
+    sessionStorage.setItem(key, JSON.stringify(data))
+    this.keys.push(key)
+    this.setLength(length + 1)
+    return data
+  }
+
+  pop () {
+    let length = this.getLength()
+    let data = this.get(length - 1)
+    this.setLength(length - 1)
+    return data
+  }
+
+  get (index) {
+    let key = this.getKey(index)
+    return JSON.parse(sessionStorage.getItem(key))
+  }
+
+  clear() {
+    this.setLength(0)
+    this.keys.forEach(key => sessionStorage.removeItem(key))
+    this.keys = [ this.length ]
+  }
+}
+
+export default Stack

+ 344 - 0
src/CAD/core/base/state.ts

@@ -0,0 +1,344 @@
+import {type, debounce} from '../util'
+
+export type SubState<T> = { [k in keyof T]: T[k] } | {}
+export type SubArgs<T> = { state: any, data: SubState<T> }
+
+export type NoticeState = { data: any, trigger: NoticeState, ret: any }
+export type NoticeArgs<T, K = any> = { args: SubState<T>, state?: { data: K, trigger: NoticeState, ret: any } }
+export interface Notice<T, K> {(args: NoticeArgs<T, K>): any}
+
+export type BeforeRet<T> = SubState<T> 
+export interface Before<T> { (stick: Array<any>,args: SubState<T>, ret: Array<SubState<T>>): BeforeRet<T> | boolean }
+
+type ListArgs<T> = [_State<T>, any, SubState<T>, string | void, SubState<T>]
+
+export interface StateArgs<T, K> {
+  // 拦截和修改通知修改的第一参数对象,默认为当前修改state
+  afferent?: any
+  childAttr?: string
+  // 修改完成通知
+  notice?: Notice<T, K>
+  // 劫持修改
+  intercept?: Before<T>
+} 
+
+export class _State<T = {}, K = any> {
+  attrs: Array<string>
+  subscribe: Array<_State<any>>
+  // 通知自身准备修改
+  intercept?: Before<T>
+  // 通知所有依赖包括自身准备修改
+  notice?: Notice<T, K>
+  // 通知所有依赖包括自身修改
+  childAttr?: string
+  // 直接通过
+  direct: boolean
+
+  private updateKeys: Set<any>
+  // 修改同意处理器
+  private setHandle: Function
+  private data: T
+  // 修改时订阅链返回this的对象
+  private afferent
+  // 修改后回调无论成功还是被阻塞都会调用
+  private updateNotices: Array<Function> = []
+  // 成功修改后的回调,失败则不会调用,每次都会清空,不一定会调用
+  private changeNotices: Array<Function> = []
+
+  __firstBind: boolean
+  test: Function
+  
+
+  constructor(props: T, {notice, intercept, childAttr, afferent}: StateArgs<T, K>) {
+    this.data = props
+    // 所有订阅者
+    this.subscribe = []
+    // 所有正在修改的keys 
+    this.updateKeys = new Set()
+    this.attrs = []
+    this.intercept = intercept
+    this.afferent = afferent
+    this.notice = notice
+    this.direct = false
+
+    this.childAttr = childAttr
+    this.deleteUpdateKey = this.deleteUpdateKey.bind(this)
+    this.addUpdateKey = this.addUpdateKey.bind(this)
+
+
+    this.changeNotices = []
+
+    this.setHandle = debounce(
+      async (args: ListArgs<T>) => {
+        let ret = await this.setVal(...args)
+        Object.keys(args[4]).forEach(this.deleteUpdateKey)
+        this.noticeTick()
+        this.noticeAll(ret || {args: {}})
+        if (ret) {
+          this.noticeChange()
+        }
+      }, 0, 
+      (args) => {
+        let {sub, current, state, childAttr} = args[0][0]
+        let data = {} as SubState<T>
+        args.forEach(([{key, val}]) => data[key] = val)
+        return [sub, current, state, childAttr, data]
+      },
+      () => {
+        this.changeNotices = []
+      }
+    )
+    return this 
+  }
+
+  // 添加监听key
+  addUpdateKey(k) {
+    this.updateKeys.add(k)
+    this.subscribe.forEach(sub => {
+      sub.updateKeys.has(this) || sub.addUpdateKey(this)
+    })
+  }
+
+  // 删除监听key
+  deleteUpdateKey(k) {
+    this.updateKeys.delete(k)
+
+    if (this.updateKeys.size === 0) {
+      this.subscribe.forEach(sub => {
+        sub.deleteUpdateKey(this)
+      })
+
+      this.test && this.test()
+    }
+  }
+
+  // 通知所有订阅的tick函数
+  noticeTick() {
+    if (this.updateKeys.size === 0) {
+      while (this.updateNotices.length) {
+        this.updateNotices.shift()()
+      }
+    }
+    this.subscribe.forEach(sub => sub.noticeTick())
+  }
+
+  // 通知所有订阅的change函数
+  noticeChange() {
+    while (this.changeNotices.length) {
+      this.changeNotices.shift()()
+    }
+    this.subscribe.forEach(sub => sub.noticeChange())
+  }
+
+  // 订阅修改后
+  nextTick(fun: Function) {
+    setTimeout(() => {
+      if (this.updateKeys.size === 0) {
+        fun()
+      } else {
+        this.updateNotices.push(fun)
+        setTimeout(() => {
+          if (~this.updateNotices.indexOf(fun)) {
+            let fns = this.updateNotices.slice(this.updateNotices.indexOf(fun), 1)
+            fun()
+          }
+        }, 200)
+      }
+    })
+  }
+
+  nextChange(fun: Function) {
+    this.changeNotices.push(fun)
+  }
+
+  // 获取所有订阅者包括下几级
+  getSubAll = () : Array<_State> => {
+    let subs = []
+    this.subscribe.forEach(sub => {
+      subs.push(...sub.getSubAll())
+    })
+    return [...this.subscribe, ...subs]
+  }
+
+  // 修改前给与拦截处理
+  async interceptAll (args: SubState<T>) : Promise<Array<SubState<T>> | false> {
+    // stick = [...stick, this.afferent]
+    let ret: Array<SubState<T>> = []
+    let stick: Array<any> = []
+    let cret, trigs = [this, ...this.getSubAll()];
+
+    for (let state, i = 0; state = trigs[i]; i++) {
+      stick.push(state.afferent)
+
+      if (!state.intercept) {
+        ret.push(void 0)
+        continue;
+      }
+
+      cret = state.intercept(stick, args, ret)
+      ret.push(cret)
+
+      if (type.isBoolean(cret) && !cret) return false;
+    }
+
+    return ret
+  }
+
+  // 修改完成后给与发布
+  noticeAll(args: NoticeArgs<T, K>): NoticeArgs<T, K> {
+    args = {...args, state: {data: this.afferent || this, trigger: args.state, ret: void 0 }}
+    // if (this.updateKeys.size === 0 && this.notice) {
+      let ret = this.notice(args)
+      args.state.ret = ret
+    // }
+    
+    for (let i = 0; i < this.subscribe.length; i++) {
+      this.subscribe[i].noticeAll(args)
+    }
+    return args
+  }
+
+  // 监听所有属性变化
+  
+  bindDataChange() {
+    this.__firstBind = true
+    Object.keys(this.data).forEach(key => {
+      this.attrs.push(key)
+      this.bindItem(this, this, this.data, key, this.childAttr)
+    })
+    this.__firstBind = false
+  }
+
+  // 获取自身和依赖者对象
+  getSeftSub (): Array<any> {
+    let subs = [this, this.subscribe.flatMap(sub => sub.getSeftSub())]
+    return subs
+  }
+
+  // 给props重新绑定值
+  private async setVal(sub: ListArgs<T>[0], current: ListArgs<T>[1], state: ListArgs<T>[2], childAttr: ListArgs<T>[3], data: any) {
+    let args = {args: data}
+    // 如果只给X 或者只给Y则从缓存钟去除 Y X来传递
+    if (!type.isUndefined(data.x) && type.isUndefined(data.y)) {
+      data.y = this.afferent.y
+    } else if (!type.isUndefined(data.y) && type.isUndefined(data.x)) {
+      data.x = this.afferent.x
+    }
+    let ret = await sub.interceptAll(data)
+
+    if (ret) {
+
+      let interRet = {...data}
+      ret.forEach(val => interRet = {...interRet, ...val})
+      Object.keys(state).forEach(k => {
+        if (interRet.hasOwnProperty(k) && state[k] !== interRet[k]) {
+          this.relieveOldSubs(state[k], childAttr)
+          state[k] = interRet[k]
+          this.continueListen(sub, current, state, k, childAttr, interRet[k])
+        }
+      })
+      return args
+    } else {
+
+      return null
+    }
+  }
+
+  // 解除绑定
+  private relieveOldSubs(val, childAttr) {
+    if (type.isUndefined(val)) return;
+    
+    if (val instanceof _State || (childAttr && val[childAttr] instanceof _State)) {
+      this.relieveOldSub(val, childAttr)
+    } else if (type.isArray(val)) {
+      val.forEach((s, i) => {
+        this.relieveOldSub(s, childAttr)
+      })
+    } else if (type.isObject(val)) {
+      Object.values(val).forEach(v => {
+        this.relieveOldSub(v, childAttr)
+      })
+    }
+  }
+
+  // 解除绑定单项
+  private relieveOldSub(val, childAttr) {
+    if (val instanceof _State) {
+      val.subscribe.splice(val.subscribe.indexOf(this), 1)
+    } else if (childAttr && val[childAttr] instanceof _State) {
+      val[childAttr].subscribe.splice(val[childAttr].subscribe.indexOf(this), 1)
+    }
+  }
+
+  // 继续监听
+  private continueListen (sub, current, state, key, childAttr, val) {
+    if (type.isNull(val) || type.isUndefined(val)) return;
+    
+    if (val instanceof _State || (childAttr && val[childAttr] instanceof _State)) {
+      let subs = (val instanceof _State ? val : val[childAttr]).subscribe
+      ~subs.indexOf(sub) || this.bindItem(sub, current[key], state, key, childAttr, true)
+    } else if (type.isArray(val)) {
+      val.forEach((s, i) => {
+        this.bindItem(sub, current[key], val, i, childAttr, true)
+      })
+    } else if (type.isObject(val)) {
+      Object.keys(state[key]).forEach(k => {
+        this.bindItem(sub, current[key], val, k, childAttr, true)
+      })
+    }
+  }
+
+  // 绑定劫持
+  bindItem (sub, current, state, key, childAttr, interrupt = false) {
+    if (state instanceof _State) {
+      state.subscribe.push(sub)
+    } else if (childAttr && state[key] && state[key][childAttr] instanceof _State) {
+      state[key][childAttr].subscribe.push(sub)
+    }
+    if (!interrupt){
+      Object.defineProperty(current, key, {
+        get () {
+          return state[key]
+        },
+        set (val) {
+          if (this.__firstBind || this.direct) {
+            state[key] = val
+            this.continueListen(sub, current, state, key, childAttr, val)
+          } else {
+            this.addUpdateKey(key)
+            this.setHandle({sub, current, state, key, childAttr, val})
+          }
+        }
+      })
+
+      this.continueListen(sub, current, state, key, childAttr, state[key])
+    }
+  }
+}
+
+// 绑定getter方法
+export function getter(obj: Object, state: _State) {
+  state.attrs.forEach(key => {
+    Object.defineProperty(obj, key, {
+      configurable: true,
+      get () {
+        return state[key]
+      }
+    })
+  })
+}
+
+// 绑定setter方法
+export function setter(obj: Object, state: _State) {
+  state.attrs.forEach(key => {
+    Object.defineProperty(obj, key, {
+      configurable: true,
+      set (val) {
+        state[key] = val
+      }
+    })
+  })
+}
+
+export type StateTS<T, K = any> = _State<T, K> & T
+export const State = _State as ({new <T, K=any>(props: T, args: StateArgs<T, K>): StateTS<T, K>})

+ 69 - 0
src/CAD/core/constant/Element.ts

@@ -0,0 +1,69 @@
+import { i18n } from '../../../../lang/index'
+export const SVGURI = 'http://www.w3.org/2000/svg'
+export const SVGPATH = "http://www.w3.org/1999/xlink"
+export const STATE_CHILD = '__state'
+export const POINT = 'point'
+export const DOOR = 'door'
+export const COLUMN = 'column'
+export const CASEMENT = 'casement'
+export const SLIDEDOOR = 'slideDoor'
+export const TAGGING = 'tagging'
+export const GROUNDCASE = 'groundCase'
+export const BAYCASE = 'bayCase'
+export const FURNCOLUMN = 'furnColumn'
+export const FURNFLUE = 'furnFlue'
+export const LINE = 'line'
+export const SEFTLINE = 'seftline'
+
+export const ARCH = {
+  [POINT]: i18n.t('modules.model.point'),
+  [DOOR]: i18n.t('modules.model.door'),
+  [COLUMN]: i18n.t('modules.model.column'),
+  [CASEMENT]: i18n.t('modules.model.casement'),
+  [SLIDEDOOR]: i18n.t('modules.model.slideDoor'),
+  [TAGGING]: i18n.t('modules.model.tagging'),
+  [GROUNDCASE]: i18n.t('modules.model.groundCase'),
+  [BAYCASE]: i18n.t('modules.model.bayCase'),
+  [FURNCOLUMN]: i18n.t('modules.model.furnColumn'),
+  [FURNFLUE]: i18n.t('modules.model.furnFlue'),
+  [LINE]: i18n.t('modules.model.line'),
+  [SEFTLINE]: i18n.t('modules.model.line')
+}
+
+export const DEFAULT = {
+  [DOOR]: {
+    width: 0.8,
+    height: 2
+  },
+  [SLIDEDOOR]: {
+    width: 1.5,
+    height: 2
+  },
+  [CASEMENT]: {
+    width: 0.8,
+    height: 1.2
+  },
+  [BAYCASE]: {
+    width: 1.5,
+    height: 1.2
+  },
+  [GROUNDCASE]: {
+    width: 1.5,
+    height: 2
+  },
+  [COLUMN]: {
+    width: 0.65,
+    tick: 0.65
+  },
+  [FURNCOLUMN]: {
+    width: 0.65,
+    tick: 0.65
+  },
+  [FURNFLUE]: {
+    width: 0.65,
+    tick: 0.65
+  },
+  [LINE]: {
+    width: 0.5
+  }
+}

+ 253 - 0
src/CAD/core/core/element.ts

@@ -0,0 +1,253 @@
+import {State, getter, setter, StateTS, NoticeArgs, SubState, BeforeRet} from '../base/state'
+import {STATE_CHILD} from '../constant/Element'
+import Renderer from '../base/renderer'
+import {throttle} from '../util'
+type Event = 'enter' | 'leave' | 'click' | 'drag'
+
+let id = 0
+
+const eventMap = {
+  'enter': 'mouseenter',
+  'leave': 'mouseleave',
+  'click': 'click',
+  'drag': 'mousedown'
+}
+
+export interface ElementProps {
+  renderer: Renderer
+}
+
+class _CADElement<T = {}> {
+  [x: string]: any
+  zIndex: number
+  __draging: boolean
+  __state: StateTS<T, _CADElement<T>>
+  select: boolean
+  real: SVGElement
+  multiple: number
+  id: number
+  renderer: Renderer
+  watch: {[key: string]: () => void}
+
+  // 是否需要保存状态变化
+  grentNode?(): SVGElement
+
+  // 存放所有实例
+  static examples = new Map<Renderer, Array<_CADElement>>()
+  static multiples = new Map<Renderer, number>()
+  static update = (renderer: Renderer, eles = []) => {
+    let all = _CADElement.examples.get(renderer).concat(eles)
+    all = Array.from(new Set(all))
+    all.forEach(ele => {
+      ele.update && ele.update()
+    })
+  }
+  static init = (renderer: Renderer) => {
+    _CADElement.examples.set(renderer, [])
+  }
+
+  constructor(props : T & ElementProps) {
+    this.zIndex = 0
+    this.render = props.renderer
+    this.id = ++id
+    // delete props.renderer
+    this.__state = new State<T, _CADElement<T> & {select: boolean}>({...props, select: false}, {
+      // 拦截和修改通知修改的第一参数对象,默认为当前修改state
+      afferent: this,
+      // 当修改props时订阅的通知
+      notice: (...args) => {
+        this.notice && this.notice(...args)
+        this.update && this.update()
+
+        let keys = Object.keys(args[0].args);
+        // (~keys.indexOf('x') || ~keys.indexOf('y')) && this.wallPos && 
+
+        if ((~keys.indexOf('x') || ~keys.indexOf('y')) && this.wallPos) {
+          try {
+            this.wallPos();
+          } catch {}
+        }
+        if ((~keys.indexOf('select')) && this.wallSelect) {
+          this.wallSelect()
+        } 
+      },
+      // 多个state关联依赖属性
+      childAttr: STATE_CHILD,
+      // 劫持修改
+      intercept: this.intercept && ((...args) => this.intercept(...args))
+    })
+    this.__state.bindDataChange()
+
+    getter(this, this.__state)
+    setter(this, this.__state)
+    
+    Object.defineProperties(this, {
+      multiple: {
+        get: () => {
+          return this.renderer.props.multiple / this.renderer.props.scale
+        }
+      }
+    })
+    _CADElement.examples.get(this.renderer).push(this)
+    
+    if (this.grentNode) {
+      this.real = this.grentNode()
+
+      if (this.setHoverStyle && this.setUnHoverStyle) {
+        this.enter = () => {
+          this.real.style.cursor = 'pointer'
+          this.setHoverStyle()
+        }
+        this.leave = () => {
+          if (!this.select) {
+            this.setUnHoverStyle()
+            this.real.style.cursor = 'inherit'
+          }
+        }
+        this.wallSelect = () => {
+          if (this.select) {
+            this.real.style.cursor = 'pointer'
+            this.setHoverStyle()
+          } else {
+            this.real.style.cursor = 'inherit'
+            this.setUnHoverStyle()
+          }
+        }
+      }
+
+      this.listen()
+    }
+
+    this.real.id = 'ele' + id.toString()
+  }
+  
+  intercept(stick: Array<any>,args: SubState<T>, ret: SubState<T>, dire?): BeforeRet<T> | boolean {
+    return args
+  }
+
+  changeSelect(select: boolean) {
+    if (this.select !== select) {
+      this.__state.direct = true
+      this.select = select
+      this.update && this.update()
+      this.__state.direct = false
+      this.wallSelect && this.wallSelect()
+
+      if (select) {
+        _CADElement.examples.get(this.renderer).forEach(e => {
+          if (e !== this && e.select) {
+            e.changeSelect(false)
+          }
+        })
+      }
+    }
+  }
+  click(ev: SVGElementEventMap['click']) {
+    this.changeSelect(true)
+    ev.stopPropagation()
+  }
+
+  nextTick(fun) {
+    this.__state.nextTick(fun)
+  }
+
+  nextChange(fun) {
+    this.__state.nextChange(fun)
+  }
+
+  // 绑定所有事件
+  listen () {
+    this.__draging = false
+
+    this.bindEvent('enter')
+    this.bindEvent('leave')
+    this.bindEvent('click')
+    this.bindEvent('drag')
+  }
+
+  unEvent() {
+    this.unbindEvent('enter')
+    this.unbindEvent('leave')
+    this.unbindEvent('click')
+    this.unbindEvent('drag')
+  }
+
+  // 销毁时解除事件
+  destroy() {
+    this.__destroy = true
+    this.unEvent()
+    let element = _CADElement.examples.get(this.renderer)
+    ~element.indexOf(this) && element.splice(element.indexOf(this), 1)
+  }
+
+  // 处理绑定事件
+  private bindDrag(ev: SVGElementEventMap['mousedown']) {
+    let start = { x: ev.offsetX, y: ev.offsetY }
+    
+    this['dragStart'] && this['dragStart'](ev, start)
+
+    let moveHandle = throttle((ev: SVGElementEventMap['mousemove']) => {
+      let end = {x: ev.offsetX, y: ev.offsetY}
+      this.__draging = true
+      this['drag']({x: end.x - start.x, y: end.y - start.y}, end, start)
+      ev.preventDefault()
+    }, 10)
+
+    let upHandle = (ev: SVGElementEventMap['mouseup']) => {
+      document.documentElement.removeEventListener('mousemove', moveHandle, false)
+      document.documentElement.removeEventListener('mouseup', upHandle, false)
+
+      this.__draging = false
+      this['dragEnd'] && this['dragEnd']({x: ev.offsetX, y: ev.offsetY})
+      this.__leave && this['leave']()
+    }
+
+    document.documentElement.addEventListener('mousemove', moveHandle, false)
+    document.documentElement.addEventListener('mouseup', upHandle, false)
+    ev.preventDefault()
+  }
+
+  // 绑定事件
+  bindEvent(event: Event) {
+    if (this[event]) {
+      let relCb = `__bind_${event}`
+      this[relCb] && this.unbindEvent(event)
+
+      // 拖拽事件需要特殊处理
+      let bindEvent = event === 'drag' ? this.bindDrag.bind(this) : (ev) => {
+        let elements = _CADElement.examples.get(this.renderer)
+        if (event === 'enter' && elements.some(e => e.__draging)) return;
+
+        if (this.__draging && (event === 'enter' || event === 'leave')) {
+          this.__leave = event === 'leave'
+          return;
+        }
+        this[event](ev)
+      }
+      this.real.addEventListener(eventMap[event], bindEvent, false);
+      this[relCb] = bindEvent
+    }
+  }
+
+  // 解绑事件
+  unbindEvent(event: Event) {
+    this[`__bind_${event}`] && this.real.removeEventListener(eventMap[event], this[`__bind_${event}`], false)
+  }
+}
+
+export type extand<T> = {
+  intercept(stick: Array<any>,args: SubState<T>, ret: SubState<T>, dire?): BeforeRet<T> | boolean
+  notice(args: NoticeArgs<T>): any 
+  update(): any 
+  wallSelect(): any 
+  wallPos(): any 
+  setUnHoverStyle(): any
+  setHoverStyle(): any
+}
+export type CADElementTS<T={}>  = _CADElement<T> & T & extand<T>
+export const CADElement = _CADElement as unknown as {
+  new <T={}, K = any>(data: T): CADElementTS<T>
+  update: (renderer: Renderer, eles?: any) => {}
+  init:  (renderer: Renderer) => {}
+  examples: Map<Renderer, Array<_CADElement>>
+}

+ 51 - 0
src/CAD/core/core/fixedline.ts

@@ -0,0 +1,51 @@
+import {CADElement, ElementProps} from './element'
+import {SVGURI} from '../constant/Element'
+import Point from './point'
+import Renderer from '../base/renderer'
+
+
+export interface SeftProps {
+  color?: string,
+  width?: number,
+  linecap?: string
+}
+
+export interface LineProps extends SeftProps, ElementProps {
+  points: [Point, Point]
+  showAngle?: boolean
+}
+
+
+class Line extends CADElement<LineProps> {
+  rel: SVGPathElement
+  init: SeftProps
+  
+  static Setting = new Map<Renderer, SeftProps>()
+
+  constructor({color, width, showAngle = false, linecap = 'square', ...args}: LineProps) {
+    width = width || Line.Setting.get(args.renderer).width
+    color = color || Line.Setting.get(args.renderer).color
+
+    super({color, width, linecap, showAngle, ...args})
+    this.init = { color, width, linecap }
+  }
+  
+  intercept(trgs: Array<any>, attr: any, rets) {
+    return true
+  }
+
+  grentNode(): SVGElement {
+    return document.createElementNS(SVGURI, 'path')
+  }
+
+  update() {
+    let width = this.width * this.multiple
+    this.real.setAttribute('stroke', this.color)
+    this.real.setAttribute('stroke-width', width.toString())
+    this.real.setAttribute('stroke-linecap', this.linecap)
+    this.real.setAttribute('d', `M ${this.points[0].x} ${this.points[0].y} L ${this.points[1].x} ${this.points[1].y}`)
+
+  }
+}
+
+export default Line

+ 71 - 0
src/CAD/core/core/fixedpoint.ts

@@ -0,0 +1,71 @@
+import {CADElement, ElementProps} from './element'
+import {SVGURI} from '../constant/Element'
+import Renderer from '../base/renderer'
+
+export interface SeftProps {
+  r?: number,
+  r1?: number,
+  strokeWidth?: number,
+  fillColor?: string,
+  storkeColor?: string
+}
+export interface point extends SeftProps {
+  x: number,
+  y: number
+}
+let a = 1;
+
+export interface PointProps extends point, ElementProps { }
+
+class Point extends CADElement<PointProps, Point> {
+  init: SeftProps
+  real: SVGEllipseElement
+
+  static fillColor = 'rgb(0, 200, 175)'
+  static storkeColor = 'green'
+
+  static Setting = new Map<Renderer, SeftProps>()
+
+  constructor({strokeWidth = 0, r = 4, r1 = 4, fillColor, storkeColor, ...args}: PointProps) {
+    fillColor = fillColor || Point.Setting.get(args.renderer).fillColor
+    storkeColor = storkeColor || Point.Setting.get(args.renderer).storkeColor
+
+    super({storkeColor, fillColor, r, r1, strokeWidth, ...args})
+    this.init = { strokeWidth, r, r1, fillColor, storkeColor }
+    this.update()
+  }
+
+  grentNode() {
+    let point = document.createElementNS(SVGURI, 'ellipse')
+    point.setAttribute('_id', (a++).toString())
+    return point
+  }
+
+  update() {
+    let r1 = this.r * this.multiple
+    let r2 = this.r1 * this.multiple
+
+    let strokeWidth = this.strokeWidth * this.multiple
+
+    try {
+    this.real.setAttribute('cx', this.x.toString())
+    this.real.setAttribute('cy', this.y.toString())
+    this.real.setAttribute('fill', this.fillColor)
+    this.real.setAttribute('rx', r1.toString())
+    this.real.setAttribute('ry', r2.toString())
+    this.real.setAttribute('stroke-width', strokeWidth.toString())
+    this.real.setAttribute('stroke', this.storkeColor)
+    } catch (e) {
+      console.error(this)
+    }
+
+
+  }
+
+  destroy() {
+    super.destroy()
+  }
+}
+
+
+export default Point

+ 53 - 0
src/CAD/core/core/line.ts

@@ -0,0 +1,53 @@
+import FixedLine, {LineProps as FixedLineProps, SeftProps} from './fixedline'
+import {point} from './fixedpoint'
+
+interface LineProps extends FixedLineProps {
+  hover?: SeftProps
+}
+
+class Line extends FixedLine {
+  hover: SeftProps
+  disable: boolean
+
+
+  private dragStartPoints: Array<point>
+  constructor({hover, color = '#fff', width = 3,  linecap, ...args }: LineProps) {
+    let init = {color, width, linecap}
+    hover = hover || {...init, color: 'rgba(243, 255, 0, 0.8)'}
+    super({width, color, ...args})
+    this.hover = hover
+    this.init = init
+    this.disable = false
+    this.real.setAttribute('class', 'variable')
+  }
+  
+  setHoverStyle() {
+    Object.keys(this.hover).forEach(k => this[k] = this.hover[k])
+  }
+
+  setUnHoverStyle() {
+    Object.keys(this.init).forEach(k => this[k] = this.init[k])
+  }
+
+
+  dragStart() {
+    this.dragStartPoints = this.points.map(point => ({x: point.x, y: point.y}))
+  }
+
+  drag(offset) {
+    if (!this.disable) {
+      this.points.forEach((point, i) => {
+        point.x = this.dragStartPoints[i].x + offset.x * this.multiple
+        point.y = this.dragStartPoints[i].y + offset.y * this.multiple
+      })
+    } else {
+      return false
+    }
+  }
+
+  dragEnd() {
+    delete this.dragStartPoints
+  }
+}
+
+export default Line

+ 37 - 0
src/CAD/core/core/linepoint.ts

@@ -0,0 +1,37 @@
+import Point, {PointProps} from './point'
+import {point} from './fixedpoint'
+import Line from './fixedline'
+import {type} from '../util'
+import {getLinePoint, isContainPoint} from '../geometry'
+
+class LinePoint extends Point {
+  line: Line
+
+  constructor(props: PointProps, line: Line) {
+    super(props)
+    this.line = line
+  }
+
+  // 获取沿线线内的点
+  getLineInsertPoint(point: point = this) {
+    let newPoint = getLinePoint(this.line, point)
+    return newPoint
+    
+    if (isContainPoint(this.line, newPoint)) {
+      return newPoint
+    } else {
+      return false
+    }
+  }
+
+  intercept(stick, {x, y}) {
+    if (type.isUndefined(x) || type.isUndefined(y)) {
+      return true
+    } else {
+      return this.getLineInsertPoint({x, y})
+    }
+  }
+}
+
+
+export default LinePoint

+ 166 - 0
src/CAD/core/core/point.ts

@@ -0,0 +1,166 @@
+import FixedPoint, { point, PointProps as FixedPointProps, SeftProps } from './fixedpoint'
+import Renderer from '../base/renderer'
+import { type } from '../util'
+import { getAngle, getLinePoint, getVectorPosPoint, lineDis, verticalLine } from '../geometry'
+import processing from '../base/processing'
+
+const CorrectAngle = 5
+
+export interface PointProps extends FixedPointProps {
+  hover?: SeftProps
+}
+
+class Point extends FixedPoint {
+  adopt: boolean //不用通过检测
+  hover: SeftProps
+  dragStartPoint: point
+  disable: boolean
+
+  static Setting = new Map<Renderer, SeftProps>()
+
+  constructor({ hover, ...args }: PointProps) {
+    if (!args.renderer) throw args;
+    if (!hover) {
+      hover = {
+        fillColor: Point.Setting.get(args.renderer).fillColor,
+        storkeColor: Point.Setting.get(args.renderer).storkeColor
+      }
+    }
+    super(args)
+    this.hover = hover
+    this.hover.r = this.hover.r || this.init.r
+    this.real.setAttribute('class', 'variable')
+    this.adopt = false
+    this.disable = false
+    this.zIndex = 2
+  }
+
+  setHoverStyle() {
+    this.fillColor = this.hover.fillColor
+    this.strokeWidth = this.hover.r
+    this.storkeColor = this.hover.storkeColor
+  }
+
+  setUnHoverStyle() {
+    this.fillColor = this.init.fillColor
+    this.strokeWidth = this.init.strokeWidth
+    this.storkeColor = this.init.storkeColor
+  }
+
+  dragStart() {
+    this.dragStartPoint = { x: this.x, y: this.y }
+  }
+
+  drag(offset) {
+    if (!this.disable && (offset.x !== 0 || offset.y !== 0)) {
+      this.x = this.dragStartPoint.x + offset.x * this.multiple
+      this.y = this.dragStartPoint.y + offset.y * this.multiple
+    } else {
+      return false
+    }
+  }
+
+  update() {
+    super.update()
+
+    let lines = this.renderer.processing.lines.filter(({ele}) => ~ele.points.indexOf(this)).map(({ele}) => ele)
+    lines.forEach(line => {
+      line.showAngle = this.hover && this.fillColor === this.hover.fillColor
+    })
+  }
+
+  intercept(trgs: Array<any>, { x, y, select }, rets): any {
+    if (type.isUndefined(x) || type.isUndefined(y)) return true;
+
+    if (!this.dragStartPoint) return;
+
+    let updPoint = { x, y }
+    let lines = this.renderer.processing.lines.map(({ ele }) => ele)
+    let checkLines = lines
+      .filter(line => ~line.points.indexOf(this))
+      .map(line => {
+        let index = line.points.indexOf(this)
+        let otindex = Number(!index)
+        let joinLines = lines.filter(cline => ~cline.points.indexOf(line.points[otindex]) && line !== cline)
+
+        let points = [...line.points] as any
+        points[index] = updPoint
+        return [{ points }, ...joinLines]
+      });
+
+    let ret
+    for (let i = 0; i < checkLines.length; i++) {
+      let lines = checkLines[i]
+      if (lines.length < 2) continue;
+      let angles = lines.slice(1)
+        .map(cline => {
+          let angle = getAngle(lines[0], cline)
+          if (angle > 90) angle = 180 - angle
+          return { line: cline, angle: getAngle(lines[0], cline) }
+        })
+
+      let levelAngles = angles
+        .map(item => ({ ...item, angle: item.angle > 90 ? 180 - item.angle : item.angle }))
+        .sort(({ angle: angle1 }, { angle: angle2 }) => angle1 - angle2)
+
+      if (levelAngles[0].angle <= CorrectAngle) {
+        let cline = levelAngles[0].line
+        ret = getLinePoint(cline, ret ? ret : updPoint)
+      }
+
+      let item = angles.find(({ angle }) => angle > 90 - CorrectAngle && angle < 90 + CorrectAngle && angle !== 90)
+
+      if (item) {
+        ret = getVectorPosPoint(
+          verticalLine(item.line),
+          item.line.points.find(point => ~lines[0].points.indexOf(point)),
+          ret ? ret : updPoint
+        )
+      }
+    }
+
+    return ret
+  }
+  async dragEnd() {
+    delete this.dragStartPoint
+    const LineArch = require('../architecture/linearch').default
+
+    let point = this
+    if (point.stopDragEnd || point.dragEnding) return;
+
+    point.dragEnding = true
+    let moreLine = point.__join_lines && point.__join_lines.find(line =>
+      !(lineDis({ points: line.points }) > 0.1 || (LineArch.attaArch.get(line as any) && LineArch.attaArch.get(line as any).length))
+    )
+
+
+
+    if (moreLine) {
+      let point = await moreLine.judgeMerge()
+      if (point) {
+        await new Promise(r => {
+          point.adopt = false
+          moreLine.nextTick(() => {
+            point.nextTick(() => {
+              moreLine.destroy()
+              moreLine.destoryPoint(point)
+              r()
+            })
+          })
+        })
+      }
+    } else {
+      let lines = this.renderer.processing.lines.map(({ele}) => ele).filter(line => ~line.points.indexOf(this))
+      
+      for (let i = 0; i < lines.length; i++) {
+        await lines[i].judgePoint()
+      }
+    }
+    point.dragEnding = false
+    this.renderer.processing.depLines()
+  }
+
+}
+
+
+export default Point

+ 524 - 0
src/CAD/core/core/wallfixedline.ts

@@ -0,0 +1,524 @@
+import FixedLine, { LineProps, SeftProps } from './fixedline'
+import Point from './point'
+import { lineDis, segmentsIntr, Point as geoPoint, pointLineDis, isFaceIntersect, getLinePoint, getAngle, getDisPointLinePoints, getLineDisSelectPoint, lineCenter } from '../geometry'
+import { type } from '../util'
+import LineArch from '../architecture/linearch'
+import { SVGURI } from '../constant/Element'
+
+interface WallLineProps extends LineProps {
+  border?: boolean;
+  exterior?: boolean;
+  isOut: boolean,
+  hover?: SeftProps
+}
+
+class WallLine extends FixedLine {
+  border: boolean
+  hover: SeftProps
+  isOut: boolean
+  adopt: boolean
+  ground: SVGGElement
+  origin: SVGPathElement
+  temp: SVGGElement
+  
+  exterior: boolean
+  static minWidth = 0.1
+  wallLines: Array<WallLine>
+
+  // isOut是否为镂空线段
+  constructor({ isOut, hover, width = 3, border = false, exterior, ...props }: WallLineProps) {
+    super({ ...props, width })
+    this.hover = hover || { ...this.init, color: 'rgba(243, 255, 0, 0.8)' }
+    this.isOut = isOut
+    this.adopt = false
+    this.border = border
+    this.exterior = exterior
+    this.listenPointDrag()
+
+    Object.defineProperty(this, 'wallLines', {
+      get() {
+        return props.renderer.processing.lines.map(({ ele }) => ele)
+      }
+    })
+  }
+
+
+  setHoverStyle() {
+    this.color = this.hover.color
+    this.width = this.hover.width
+    this.linecap = this.hover.linecap
+  }
+
+  setUnHoverStyle() {
+    this.color = this.init.color
+    this.width = this.init.width
+    this.linecap = this.init.linecap
+  }
+
+  async destoryPoint(point: Point) {
+    // let id = this.renderer.processing.points.find(({ele}) => ele === point).id
+    // let ret = this.renderer.processing.data.room
+    //   .some(room => {
+    //     if (~room.ground.indexOf(id)) {
+    //       return true
+    //     }
+    //     return room.hole.some(({pos}) => ~pos.indexOf(id))
+    //   })
+
+    // ret || 
+    point.destroy()
+  }
+
+  // 43
+
+
+  listenPointDrag(points: Array<Point> = this.points) {
+    points.forEach(point => {
+      // 点关联的线条记录
+      if (point.__join_lines) {
+        if (!~point.__join_lines.indexOf(this)) {
+          point.__join_lines.push(this)
+        }
+      } else {
+        point.__join_lines = [this]
+      }
+
+      // 当拖拽结束检查关联线是否需要合并,公用线不允许先合并
+    })
+  }
+
+  // 获取应该删除哪个点位
+  getDelRetain() {
+    return {
+      delIndex: 0,
+      retainIndex: 1
+    }
+  }
+
+  async judgeMerge() {
+    if (this.points[0] === this.points[1] || lineDis({ points: this.points }) > WallLine.minWidth || (LineArch.attaArch.get(this as any) && LineArch.attaArch.get(this as any).length)) {
+      return;
+    }
+    let lines = this.wallLines
+    let delIndex, retainIndex
+    // let delLines = []
+    // let updateLine = []
+    try {
+      let indexO = this.getDelRetain()
+      delIndex = indexO.delIndex
+      retainIndex = indexO.retainIndex
+    } catch (e) {
+      return;
+    }
+
+    let retainPoint = this.points[retainIndex]
+
+    await Promise.all(
+      lines.map((line) => {
+        let index;
+        if (line !== this && ~(index = line.points.indexOf(this.points[delIndex]))) {
+          return new Promise(r => {
+            line.nextTick(() => {
+              let points = [...line.points] as [Point, Point]
+              points[index] = this.points[retainIndex]
+
+              if (points[delIndex] === points[retainIndex]) {
+                line.destroy()
+              } else {
+                line.renderer.processing.updateWallPoints(line as any, line.points, points)
+                line.points = points
+                line.listenPointDrag(points)
+                line.update()
+                line.nextTick(() => line.updateJoin())
+              }
+              r()
+            })
+          })
+        }
+      })
+    )
+
+    this.points[delIndex].x = this.points[retainIndex].x
+    this.points[delIndex].y = this.points[retainIndex].y
+    this.points[delIndex].adopt = true
+
+
+    setTimeout(() => {
+      if (!this.wallLines.some(line => ~line.points.indexOf(retainPoint))) {
+        this.points[retainIndex].destroy()
+      }
+    }, 500)
+    return this.points[delIndex]
+  }
+
+
+  async judgePoint() {
+    for (let i = 0; i < this.points.length; i++) {
+      let tempPoint = this.points[i]
+
+      if (!this.renderer.processing.points.some(({ ele }) => ele === tempPoint)) continue;
+
+      let processing = this.renderer.processing
+      let joinLines = [...this.wallLines].filter(line => line !== this && !line.exterior)
+      let MINDIS = 0.1
+
+      joinLines = joinLines.filter(splitLine => pointLineDis(splitLine, tempPoint) <= MINDIS)
+
+      for (let i = 0; i < joinLines.length; i++) {
+        let splitLine = joinLines[i]
+
+        let clines = processing.lines.filter(({ ele }) => ~ele.points.indexOf(splitLine.points[0]) && ele.points.indexOf(splitLine.points[1]))
+          .map(({ ele }) => ele)
+        let archs = []
+        clines.forEach(line => archs = archs.concat(LineArch.attaArch.get(line) || []))
+
+        let updatePos = getLinePoint(splitLine, tempPoint)
+        let stopArch = archs.find(arch => pointLineDis({ points: arch.linePoints }, updatePos) < 0.001)
+
+        if (stopArch) {
+          // let start = lineDis({ points: [tempPoint, stopArch.linePoints[0]] }) > lineDis({ points: [tempPoint, stopArch.linePoints[1]] }) ?
+          //   stopArch.linePoints[1] : stopArch.linePoints[0];
+
+          // let points = getDisVerticalLinePoints({ points: stopArch.linePoints }, start, MINDIS)
+          // let point = lineDis({ points: [tempPoint, points[0]] }) > lineDis({ points: [tempPoint, points[1]] }) ? points[1] : points[0]
+
+          // tempPoint.x = point.x
+          // tempPoint.y = point.y
+        } else if (pointLineDis(splitLine, updatePos) < 0.1) {
+          tempPoint.x = updatePos.x
+          tempPoint.y = updatePos.y
+
+          try {
+            await new Promise((r, j) => {
+              tempPoint.nextTick(() => {
+                if (!this.renderer.processing.points.some(({ ele }) => ele === tempPoint)) j();
+                let { promis, ret } = processing.lineInsertPoint(splitLine as any, tempPoint)
+                if (!ret) return r();
+                promis.then(() => {
+                  tempPoint.x = updatePos.x
+                  tempPoint.y = updatePos.y
+                  tempPoint.wallPos && tempPoint.wallPos()
+                  r()
+                })
+                splitLine.wallPos && splitLine.wallPos()
+              })
+            })
+          }catch {
+            break;
+          }
+        }
+      }
+    }
+  }
+
+  updateJoin() {
+    let lines = this.wallLines
+
+    this.points[0].__join_lines = lines
+      .filter(line => ~line.points.indexOf(this.points[0]))
+
+    this.points[1].__join_lines = lines
+      .filter(line => ~line.points.indexOf(this.points[1]))
+  }
+
+  removeJoin(line: WallLine = this) {
+    let jlines1 = line.points[0].__join_lines
+    let jlines2 = line.points[1].__join_lines
+    jlines1.splice(jlines1.indexOf(line), 1)
+    jlines2.splice(jlines2.indexOf(line), 1)
+  }
+
+  checkCross(points: [geoPoint, geoPoint] = this.points) {
+    const proc = this.renderer.processing
+    const lines = this.wallLines.filter(line => ~line.points.indexOf(this.points[0]) || ~line.points.indexOf(this.points[1]))
+
+    return lines.some(sline => {
+      let sindex1 = sline.points.indexOf(this.points[0])
+      let sindex2 = sline.points.indexOf(this.points[1])
+      let spoints = [...sline.points] as unknown as [geoPoint, geoPoint]
+
+      ~sindex1 && (spoints[sindex1] = points[0])
+      ~sindex2 && (spoints[sindex2] = points[1])
+
+
+      return this.wallLines.some(line => {
+        if (!~line.points.indexOf(sline.points[0]) && !~line.points.indexOf(sline.points[1])) {
+          let point = segmentsIntr(line, { points: spoints })
+          if (!point) return false
+
+
+          if (!(lineDis({ points: [spoints[0], point] }) === 0 ||
+            lineDis({ points: [spoints[1], point] }) === 0 ||
+            lineDis({ points: [line.points[0], point] }) === 0 ||
+            lineDis({ points: [line.points[1], point] }) === 0)) return true;
+
+          let pface: any = proc.getRoomsByLine(sline as any)
+          pface = pface.length ? pface : proc.getHolesByLine(sline as any)
+          pface = pface[0].ground || pface[0].pos
+          let cface: any = proc.getRoomsByLine(line as any)
+          cface = cface.length ? cface : proc.getHolesByLine(line as any)
+          cface = cface[0].ground || cface[0].pos
+
+          if (pface === cface) return false;
+
+          pface = pface.map(id => {
+            let point = proc.points.find(({ id: aid }) => id === aid).ele
+            if (point === this.points[0]) {
+              return points[0]
+            } else if (point === this.points[1]) {
+              return points[1]
+            } else {
+              return point
+            }
+          })
+          cface = cface.map(id => {
+            let point = proc.points.find(({ id: aid }) => id === aid).ele
+            if (point === this.points[0]) {
+              return points[0]
+            } else if (point === this.points[1]) {
+              return points[1]
+            } else {
+              return point
+            }
+          })
+
+          return isFaceIntersect(pface, cface)
+        }
+      })
+    })
+  }
+
+  intercept(trgs: Array<any>, { x, y, points }, rets) {
+    return true
+    if (type.isUndefined(x) || type.isUndefined(y)) return true;
+
+
+    let archs = LineArch.attaArch.get(this as any)
+
+
+    return true
+    let lines = this.wallLines
+
+
+    if (!lines.some((line) => line === this)) {
+      return true;
+    }
+
+    let point = this.points.find(point => ~trgs.indexOf(point))
+    let index = this.points.indexOf(point)
+    let seftPoints = [...this.points] as unknown as [geoPoint, geoPoint]
+    seftPoints[index] = { x, y }
+
+    let ret = (this.points[0].adopt || this.points[1].adopt) ? false : this.checkCross(seftPoints)
+
+    return ret ? false : true
+  }
+
+  grentNode() {
+    let node = document.createElementNS(SVGURI, 'g')
+    this.origin = super.grentNode() as SVGPathElement
+    this.ground = document.createElementNS(SVGURI, 'g')
+    this.temp = document.createElementNS(SVGURI, 'g')
+
+    node.appendChild(this.ground)
+    node.appendChild(this.origin)
+    node.appendChild(this.temp)
+    this.temp.setAttribute('pointer-events', 'none')
+
+    
+    return node
+  }
+
+  update() {
+    let archs = LineArch.attaArch.get(this as any)
+    let width = this.multiple * this.width * (this.border ? 2 : 1.333)
+
+    this.origin.setAttribute('stroke-width', width.toString())
+    this.origin.setAttribute('stroke-linecap', this.linecap)
+    try {
+      this.origin.setAttribute('d', `M ${this.points[0].x} ${this.points[0].y} L ${this.points[1].x} ${this.points[1].y}`)
+    } catch (e) {
+    }
+
+    if (this.exterior) {
+      this.origin.setAttribute('stroke', '#5e5e5e')
+      this.origin.setAttribute('stroke-dasharray', `${10 * this.multiple}, ${10 * this.multiple}`)
+    } else {
+      this.origin.setAttribute('stroke', this.color)
+    }
+
+
+
+    if (!this.exterior && archs && archs.length > 0) {
+      this.origin.setAttribute('stroke', 'rgba(0,0,0,0)')
+
+      let archGrouns = []
+      for (let i = 0; i < archs.length; i++) {
+        if (archGrouns.some(archs => archs.some(arch => arch === archs[i]))) continue;
+
+        let ground = archs.filter(arch => 
+          arch.linePoints && arch.linePoints.length && (
+            pointLineDis({points: archs[i].linePoints}, arch.linePoints[0]) < 0.1 ||
+            pointLineDis({points: archs[i].linePoints}, arch.linePoints[1]) < 0.1
+          )
+        )
+        ground.length && archGrouns.push(ground)
+      }
+      
+
+      let apoints = []
+      archGrouns.forEach(archs => {
+        let points = archs
+          .reduce((t, arch) => t.concat(arch.linePoints), [])
+          .sort((a, b) =>
+            lineDis({ points: [a, this.points[0]] }) - lineDis({ points: [b, this.points[0]] })
+          );
+
+        let ret = []
+        if (pointLineDis(this, points[0]) < 0.1) {
+          ret.push(points[0])
+        }
+        if (pointLineDis(this, points[points.length - 1]) < 0.1) {
+          ret.push(points[points.length - 1])
+        }
+        if (ret.length) {
+          apoints.push(ret)
+        }
+      })
+      apoints = apoints.sort((a, b) => lineDis({ points: [a[0], this.points[0]] }) - lineDis({ points: [b[0], this.points[0]] }))
+
+      this.ground.innerHTML = ''
+
+      let html = ''
+      for (let i = 1; i < apoints.length; i++) {
+        let start = apoints[i - 1][apoints[i - 1].length - 1]
+        let end = apoints[i][0]
+
+        html += `<path 
+          stroke="${this.exterior && this.color !== 'rgba(243, 255, 0, 0.8)' ? 'rgb(150,150,150)' : this.color}" 
+          stroke-width="${width}" 
+          stroke-linecap="butt" 
+          d="M ${start.x} ${start.y} L ${end.x} ${end.y}"></path>`
+      }
+
+      if (apoints.length && apoints[0].length === 2) {
+        html = `<path 
+          stroke="${this.exterior && this.color !== 'rgba(243, 255, 0, 0.8)' ? 'rgb(150,150,150)' : this.color}" 
+          stroke-width="${width}" 
+          stroke-linecap="butt" 
+          d="M ${this.points[0].x} ${this.points[0].y} L ${apoints[0][0].x} ${apoints[0][0].y}"></path>` + html
+      }
+
+      
+      if (apoints.length && apoints[apoints.length - 1].length === 2) {
+        html = html + `<path 
+          stroke="${this.exterior && this.color !== 'rgba(243, 255, 0, 0.8)' ? 'rgb(150,150,150)' : this.color}" 
+          stroke-width="${width}" 
+          stroke-linecap="butt" 
+          d="M ${apoints[apoints.length - 1][1].x} ${apoints[apoints.length - 1][1].y} L ${this.points[1].x} ${this.points[1].y}"></path>`
+      }
+
+      if (apoints.length === 1 && apoints[0].length === 1) {
+        let arch = archs.find(arch => ~arch.linePoints.indexOf(apoints[0][0])) 
+        let oth = arch.linePoints[Number(!arch.linePoints.indexOf(apoints[0][0]))]
+        let start = lineDis({points: [oth, this.points[0]]}) > lineDis({points: [oth, this.points[1]]}) ? this.points[0] : this.points[1]
+
+        html += `<path 
+          stroke="${this.exterior && this.color !== 'rgba(243, 255, 0, 0.8)' ? 'rgb(150,150,150)' : this.color}" 
+          stroke-width="${width}" 
+          stroke-linecap="butt" 
+          d="M ${start.x} ${start.y} L ${apoints[0][0].x} ${apoints[0][0].y}"></path>`
+      }
+
+      if (apoints.length === 0) {
+        html += `<path 
+          stroke="${this.exterior && this.color !== 'rgba(243, 255, 0, 0.8)' ? 'rgb(150,150,150)' : this.color}" 
+          stroke-width="${width}" 
+          stroke-linecap="butt" 
+          d="M ${this.points[0].x} ${this.points[0].y} L ${this.points[1].x} ${this.points[1].y}"></path>`
+      }
+      
+      this.ground.innerHTML = html
+    } else {
+      this.ground.innerHTML = ''
+    }
+
+    
+    this.temp.innerHTML = ''
+
+    if (!this.showAngle && this.color !== this.hover.color) return;
+
+    let joinLines = this.renderer.processing.lines.filter(line => 
+      line.ele as any !== this &&
+      (~line.ele.points.indexOf(this.points[0]) || ~line.ele.points.indexOf(this.points[1]))
+    ).map(({ele}) => ele)
+
+    if (joinLines.length === 0) return;
+
+    let dis = this.renderer.screenToRealPoint({x: 20, y: 0}).x - this.renderer.screenToRealPoint({x: 0, y: 0}).x
+
+    joinLines.forEach(line => {
+      let angle = getAngle(line, this)
+      let pubPoint = ~line.points.indexOf(this.points[0]) ? this.points[0] : this.points[1]
+      let p1 = getLineDisSelectPoint(line, pubPoint, dis)
+      let p2 = getLineDisSelectPoint(this, pubPoint, dis)
+      let center = lineCenter({points: [p1, p2]})
+
+      if (!isNaN(center.x) && !isNaN(angle) && angle > 10 && angle < 170) {
+        angle = Math.round(angle) 
+        angle = angle >= 89 && angle<=91 ? 90 : angle
+        let text = document.createElementNS(SVGURI, 'text')
+        text.setAttribute('fill', '#fff')
+        text.setAttribute('text-anchor', 'middle')
+        text.setAttribute('dominant-baseline', 'middle')
+        text.setAttribute('x', center.x.toString())
+        text.setAttribute('y', center.y.toString())
+        text.setAttribute('font-size', (9*this.multiple).toString())
+        text.textContent = parseInt(angle + '').toString() + '°'
+
+        // const fontSize = this.fontSize * this.multiple
+        this.temp.appendChild(text)
+      }
+    })
+  }
+
+  destroy() {
+    let lines = this.wallLines
+    lines.splice(lines.indexOf(this), 1)
+    
+    let archs = LineArch.attaArch.get(this as any)
+
+    if (archs) {
+      archs = [...archs]
+      let line = lines.find(line => (
+        (line.points[0] === this.points[0] && line.points[1] === this.points[1]) ||
+        (line.points[1] === this.points[0] && line.points[0] === this.points[1])
+      ))
+
+      archs.forEach(arch => {
+        if (line) {
+          arch.setAttachment(line as any)
+        } else {
+          arch.destroy()
+        }
+      })
+    }
+
+    this.nextTick(() => {
+      lines.filter(line => ~line.points.indexOf(this.points[0]) || ~line.points.indexOf(this.points[1]))
+        .forEach(line => line.updateJoin())
+    })
+
+    super.destroy()
+    this.removeJoin();
+    
+    if (!lines.some(line => ~line.points.indexOf(this.points[0]))) {
+      this.points[0].destroy()
+    }
+    if (!lines.some(line => ~line.points.indexOf(this.points[1]))) {
+      this.points[1].destroy()
+    }
+  }
+}
+
+export default WallLine

+ 447 - 0
src/CAD/core/core/wallline.ts

@@ -0,0 +1,447 @@
+import wallFixedLine from './wallfixedline'
+import LineArch from '../architecture/linearch'
+import EPoint from './point'
+import Column from '../architecture/column'
+import { SEFTLINE } from '../constant/Element'
+import {
+  Point, lineVector, verticalLine, pointLineDis, Line, getDisPointLinePoints, lineDis, segmentsIntrFine, getPointCoordDistance, getVectorPosPoint
+} from '../geometry'
+
+class WallLine extends wallFixedLine {
+  startPoint: Point
+  bkpoints: [Point, Point]
+  bkverctor: Point
+  clones: Array<number>
+
+
+  setHoverStyle() {
+    this.color = this.hover.color
+    this.width = this.hover.width
+    this.linecap = this.hover.linecap
+  }
+
+  setUnHoverStyle() {
+    this.color = this.init.color
+    this.width = this.init.width
+    this.linecap = this.init.linecap
+  }
+
+  dragStart() {
+    this.clones = []
+    this.bkpoints = this.points.map(p => ({ x: p.x, y: p.y })) as [Point, Point]
+    this.startPoint = this.renderer.screenToRealPoint({ x: 0, y: 0 })
+    this.bkverctor = lineVector(this)
+
+  }
+
+  drag(screen) {
+    let move = this.renderer.screenToRealPoint(screen)
+
+    this.changePos({ x: move.x - this.startPoint.x, y: move.y - this.startPoint.y });
+
+  }
+
+  async dragEnd() {
+    // this.correct()
+    const processing = this.renderer.processing
+
+    processing.lines.forEach(({ ele }) => {
+      ele.__state.direct = false
+    })
+    processing.points.forEach(({ ele }) => {
+      ele.__state.direct = false
+    })
+
+
+    // this.points.forEach(point => point.dragEnd())
+    setTimeout(() => {
+      this.renderer.processing.depLines()
+    }, 500)
+    return;
+  }
+
+  directUpdate(update: Function) {
+    this.__state.direct = true
+    let points = [...this.points]
+    points.forEach(point => point.__state.direct = true)
+    update()
+    points = Array.from(new Set(points.concat(this.points)))
+    points.forEach(point => point.__state.direct = false)
+    this.__state.direct = false
+    points.concat(points).forEach(point => point.update())
+    this.wallLines.filter(line => line.points.some(p => ~points.indexOf(p)))
+      .forEach(line => {
+        line.update()
+        line.points[0].wallPos && line.points[0].wallPos()
+        line.points[1].wallPos && line.points[1].wallPos()
+        line.wallPos && line.wallPos()
+        line.fromAsync && line.fromAsync()
+        line.updateJoin()
+      })
+  }
+
+  grentNewJoinLine(point: EPoint, pos: Point) {
+    let temp;
+    const proc = this.renderer.processing
+    const pointEle = proc.points.find(({ ele }) => ele === point)
+    const lineEle = proc.lines.find(({ ele }) => ele as any === this)
+    const addPointEle = proc.addPoint({
+      id: Math.max(...proc.points.map(p => p.id)) + 1,
+      ...pos
+    })
+    proc.data.vertex.push({
+      id: addPointEle.id,
+      x: pos.x,
+      y: pos.y
+    })
+    let cline = lineEle.ele
+    // console.log(ccindex, coindex)
+
+
+    const points = [...cline.points];
+    points[cline.points.indexOf(point)] = addPointEle.ele
+    cline.directUpdate(() => {
+      cline.points = points as [EPoint, EPoint]
+      cline.listenPointDrag(points)
+    })
+    cline.update()
+
+
+    temp = proc.cad.increase(SEFTLINE, {
+      id: proc.getNewLineId(),
+      p1: addPointEle.id,
+      p2: pointEle.id,
+      isOut: cline.isOut,
+      border: cline.border,
+      exterior: cline.exterior
+    }).obj
+
+
+    // const points = [...this.points];
+    // points[this.points.indexOf(point)] = addPointEle.ele
+    // this.points = points as [EPoint, EPoint]
+
+    this.wallLines.filter(line => ~line.points.indexOf(this.points[0]) || ~line.points.indexOf(this.points[1]))
+      .forEach(line => {
+        line.update()
+        line.points[0].wallPos && line.points[0].wallPos()
+        line.points[1].wallPos && line.points[1].wallPos()
+        line.wallPos && line.wallPos()
+        line.fromAsync && line.fromAsync()
+      });
+
+    return temp
+  }
+
+  getPointJoinLines(point: EPoint) {
+    let lines = this.wallLines.filter(line =>
+      ~line.points.indexOf(point) && !(~this.points.indexOf(line.points[0]) && ~this.points.indexOf(line.points[1]))
+    );
+
+    let filterLines = []
+
+    for (let i = 0; i < lines.length; i++) {
+      if (!filterLines.some(fline => ~fline.points.indexOf(lines[i].points[0]) && ~fline.points.indexOf(lines[i].points[1]))) {
+        filterLines.push(lines[i])
+      }
+    }
+
+    return filterLines
+  }
+
+  updatePoint(point: EPoint, pos: Point, joint: boolean) {
+    let pjoinLines = this.getPointJoinLines(point);
+    let temp = pjoinLines[0];
+    let lv = lineVector(this)
+    let otherIndex = Number(!this.points.indexOf(point))
+    let other = this.points[otherIndex]
+    let atwill = false
+
+
+    if (temp) {
+      let tv = lineVector(temp) || temp.lineVerctor
+      let grent = false
+      if (isNaN(tv.x) && !isNaN(lv.x)) {
+        atwill = true
+      }
+
+      let index = this.points.indexOf(point)
+      let inscope = pjoinLines.every(line => {
+        let qlv = lineVector(line)
+        return Math.abs(Math.abs(qlv.x) - Math.abs(tv.x)) < 0.2 && Math.abs(Math.abs(qlv.y) - Math.abs(tv.y)) < 0.2
+      })
+      let split = (Math.abs(Math.abs(tv.x) - Math.abs(lv.x)) < 0.2 && Math.abs(Math.abs(tv.y) - Math.abs(lv.y)) < 0.2) || (pjoinLines.length > 1 && !inscope)
+
+      if (!~this.clones.indexOf(index) && (split || atwill)) {
+        if (!atwill && pjoinLines.length > 1) {
+          let vtemp = pjoinLines.find(line => {
+            let glv = lineVector(line)
+            return glv.x - lv.x < 0.1 && glv.y - lv.y < 0.1
+          })
+          temp = vtemp || pjoinLines.find(line => {
+            let glv = lineVector(line)
+            let jx = Math.abs(glv.x - lv.x)
+            let jy = Math.abs(glv.y - lv.y)
+            return (jx > 0.1 && jx < 0.9) || (jy > 0.1 && jy < 0.9)
+          }) || pjoinLines[0]
+          tv = temp.lineVector || lineVector(temp)
+        }
+
+        let tempPos = { ...pos }
+        if (Math.abs(lv.x) > Math.abs(lv.y)) {
+          tempPos.x = point.x
+        } else {
+          tempPos.y = point.y
+        }
+
+        if (atwill) {
+          pos = tempPos
+        }
+
+        if (!(tempPos.x === point.x && tempPos.y === point.y)) {
+          temp = this.grentNewJoinLine(point, tempPos.x === point.x && tempPos.y === point.y ? pos : tempPos)
+          grent = true
+          this.clones.push(index)
+          point = temp.points[Number(!temp.points.indexOf(point))]
+          tv = lineVector(temp)
+          temp.lineVerctor = tv
+        }
+      }
+      // console.log(pos, lineVector(temp))
+      pos = getVectorPosPoint(tv, temp.points[0], pos)
+      // pos = getLinePoint(temp, pos)
+      temp.lineVector = tv
+
+      if (!grent && joint && !atwill && (Math.abs(Math.abs(tv.x) - Math.abs(lv.x)) < 0.9 || Math.abs(Math.abs(tv.y) - Math.abs(lv.y)) < 0.9)) {
+        let inter = segmentsIntrFine(
+          temp,
+          {
+            points: [
+              other,
+              {
+                x: other.x + this.bkverctor.x * 10000,
+                y: other.y + this.bkverctor.y * 10000
+              }
+            ]
+          }
+        );
+        inter && (pos = inter);
+      }
+    } else {
+      if (this.getPointJoinLines(other).length) {
+        let seftIndex = this.points.indexOf(point)
+        pos = {
+          x: this.bkpoints[seftIndex].x + this.points[otherIndex].x - this.bkpoints[otherIndex].x,
+          y: this.bkpoints[seftIndex].y + this.points[otherIndex].y - this.bkpoints[otherIndex].y
+        }
+      }
+      temp = this
+    }
+
+    let newv = lineVector({ points: [other, pos] })
+
+
+    if (isNaN(pos.x) || isNaN(pos.y) ||
+      (joint && (
+        Math.abs(Math.abs(this.bkverctor.x) - Math.abs(newv.x)) > 0.05 ||
+        Math.abs(Math.abs(this.bkverctor.y) - Math.abs(newv.y)) > 0.05
+      )
+      )) {
+      return false
+    }
+    point.__state.direct = true
+    point.x = pos.x
+    point.y = pos.y
+    point.update()
+    point.__state.direct = false
+    return temp
+  }
+
+  getVerPos(point: Point): Point {
+    let vl = verticalLine(this)
+    if (vl.x === 0) {
+      return { x: 0, y: point.y }
+    } else if (vl.y === 0) {
+      return { x: point.x, y: 0 }
+    }
+
+    let line = {
+      points: [
+        { x: vl.x * -100, y: vl.y * -100 },
+        { x: vl.x * 100, y: vl.y * 100 },
+      ]
+    } as Line
+    let dl = pointLineDis(line, point)
+    let dis = Math.sqrt(Math.pow(point.x, 2) + Math.pow(point.y, 2) - Math.pow(dl, 2))
+    let [p1, p2] = getDisPointLinePoints(line, { x: 0, y: 0 }, dis)
+
+    return lineDis({ points: [p1, point] }) > lineDis({ points: [p2, point] })
+      ? p2 : p1
+  }
+
+  checkJoinArch() {
+    let archs = LineArch.attaArch.get(this)
+
+    if (!archs || archs.length === 0) return false
+
+    let archsPs = archs.map(arch => [
+      arch.linePoints[0].getLineInsertPoint(),
+      arch.linePoints[1].getLineInsertPoint()
+    ]) as Array<[Point, Point]>
+
+    let ret = archs.some((arch, i) => {
+      if (!archsPs[i][0] ||
+        !archsPs[i][1] ||
+        !arch.qualified(archsPs[i] as [Point, Point])
+      ) return true;
+
+      if (!(arch instanceof Column)) return false;
+
+      let perPoints = arch.getPeripheral(archsPs[i].concat(arch.points.slice(2)))
+
+      return perPoints.length === 0
+    })
+
+    return ret;
+  }
+
+  updateArchs() {
+    let archs = LineArch.attaArch.get(this)
+    if (!archs || archs.length === 0) return
+
+    let archsPs = archs.map(arch => arch.lineChange().points) as Array<[Point, Point]>
+
+    archs.forEach((arch, i) => {
+      arch.__state.direct = true
+
+      arch.linePoints[0].__state.direct = true
+      arch.linePoints[1].__state.direct = true
+      arch.linePoints[0].x = archsPs[i][0].x
+      arch.linePoints[0].y = archsPs[i][0].y
+      arch.linePoints[1].x = archsPs[i][1].x
+      arch.linePoints[1].y = archsPs[i][1].y
+      arch.linePoints[0].update()
+      arch.linePoints[1].update()
+      arch.linePoints[0].__state.direct = false
+      arch.linePoints[1].__state.direct = false
+
+      if (arch instanceof Column) {
+        let points = arch.getPeripheral()
+        arch.points[2].__state.direct = true
+        arch.points[3].__state.direct = true
+        arch.points[2].x = points[0].x
+        arch.points[2].y = points[0].y
+        arch.points[3].x = points[1].x
+        arch.points[3].y = points[1].y
+        arch.points[2].update()
+        arch.points[3].update()
+        arch.points[2].__state.direct = false
+        arch.points[3].__state.direct = false
+      }
+
+      arch.__state.direct = false
+      arch.dragEnd()
+      arch.update()
+    })
+  }
+
+  changePos(point: Point) {
+    if (point.x !== 0 || point.y !== 0) {
+      point = this.getVerPos(point)
+    }
+
+    this.directUpdate(() => {
+      let checkPoints = this.points.map(p => ({ x: p.x, y: p.y }))
+      let ps = [0, 1]
+      let t1 = this.getPointJoinLines(this.points[0])
+      let t2 = this.getPointJoinLines(this.points[1])
+      let bkpoints = this.points.map(p => ({ x: p.x, y: p.y }))
+      let update = true
+
+      if (t1.length === 0) {
+        ps = [1, 0]
+      } else if (t2.length !== 0) {
+        let t1c = getPointCoordDistance(this.bkverctor, lineVector(t1[0]))
+        let t2c = getPointCoordDistance(this.bkverctor, lineVector(t2[0]))
+        if (t1c > t2c) {
+          ps = [0, 1]
+        } else {
+          ps = [1, 0]
+        }
+      }
+
+      ps.forEach((i, index) => {
+        if (!update) return;
+        if (!this.updatePoint(
+          this.points[i],
+          { x: this.bkpoints[i].x + point.x, y: this.bkpoints[i].y + point.y },
+          !!index)
+        ) {
+          update = false
+        }
+      })
+      let joinLines = this.renderer.processing.lines.filter(line => ~line.ele.points.indexOf(this.points[0]) || ~line.ele.points.indexOf(this.points[1]))
+      joinLines.forEach(line => {
+        line.ele.updateArchs()
+      })
+    })
+  }
+
+  correct() {
+    const proc = this.renderer.processing
+    const rPoints = proc.points.map(({ ele }) => ele)
+    const rLines = proc.lines.map(({ ele }) => ele)
+    const lines = rLines.filter(line => ~rPoints.indexOf(line.points[0]) || ~rPoints.indexOf(line.points[1]))
+    const check = (line, point, cpoint) => ~line.points.indexOf(point) && ~line.points.indexOf(cpoint)
+
+    for (let i = 0; i < rLines.length; i++) {
+      const line = rLines[i]
+      const repPoint = rPoints.find(point => {
+        return !~line.points.indexOf(point) && (lineDis({ points: [line.points[0], point] }) < 0.02 || lineDis({ points: [line.points[1], point] }) < 0.02)
+      })
+
+      if (!repPoint) continue;
+
+      let updateIndex = 0
+
+      if (rLines.some(cline => check(cline, repPoint, line.points[0]))) {
+        updateIndex = 0
+      } else if (rLines.some(cline => check(cline, repPoint, line.points[1]))) {
+        updateIndex = 1
+      } else {
+        continue;
+      }
+
+      const updPoint = line.points[updateIndex]
+
+
+
+      let clines = lines.filter(line => ~line.points.indexOf(updPoint))
+
+      clines.forEach(line => {
+        let index = line.points.indexOf(updPoint)
+        let oldPoints = line.points
+        let points = [...oldPoints] as [EPoint, EPoint]
+        points[index] = repPoint
+        line.directUpdate(() => {
+
+          line.points = points
+          line.listenPointDrag(points)
+        })
+      })
+
+      lines
+        .filter(line => ~line.points.indexOf(repPoint) && ~line.points.indexOf(updPoint))
+        .forEach(line => {
+          proc.lines.splice(proc.lines.findIndex(({ ele }) => ele === line), 1)
+          line.destroy()
+        });
+      updPoint.destroy()
+
+
+      return this.correct()
+    }
+  }
+}
+
+export default WallLine

+ 36 - 0
src/CAD/core/furniture/column.ts

@@ -0,0 +1,36 @@
+import furn, {seftProps} from "./furn";
+import { SVGURI } from '../constant/Element'
+
+
+class Column extends furn {
+  path1: SVGPathElement
+  path2: SVGPathElement
+
+  grentNode () {
+    let node = super.grentNode()
+
+    this.path1 = document.createElementNS(SVGURI, 'path')
+    this.path2 = document.createElementNS(SVGURI, 'path')
+
+    this.path1.setAttribute('stroke', this.stroke)
+    this.path2.setAttribute('stroke', this.stroke)
+    
+
+    node.insertBefore(this.path1, node.childNodes[0])
+    node.insertBefore(this.path2, node.childNodes[0])
+
+    return node
+  }
+
+  update() {
+    super.update()
+    this.path1.setAttribute('stroke-width', (this.width * this.multiple).toString())
+    this.path2.setAttribute('stroke-width', (this.width * this.multiple).toString())
+    this.path1.setAttribute('d', `M ${this.points[0].x} ${this.points[0].y} L ${this.points[2].x} ${this.points[2].y}`)
+    this.path2.setAttribute('d', `M ${this.points[1].x} ${this.points[1].y} L ${this.points[3].x} ${this.points[3].y}`)
+    this.path1.setAttribute('stroke', this.stroke)
+    this.path2.setAttribute('stroke', this.stroke)
+  }
+}
+
+export default Column

+ 68 - 0
src/CAD/core/furniture/flue.ts

@@ -0,0 +1,68 @@
+import furn from "./furn";
+import { SVGURI } from '../constant/Element'
+import { faceCenter, lineCenter, Line, lineDis, getDisVerticalLinePoints, pointInsideBorder, pointInside } from '../geometry'
+
+
+class Flue extends furn {
+  path1: SVGPathElement
+  path2: SVGPathElement
+  g: SVGGElement
+
+
+  grentNode () {
+    let node = super.grentNode()
+
+    this.g = document.createElementNS(SVGURI, 'g')
+    this.path1 = document.createElementNS(SVGURI, 'path')
+    this.path2 = document.createElementNS(SVGURI, 'path')
+
+    this.path1.setAttribute('stroke', this.stroke)
+    this.path1.setAttribute('fill', 'rgba(0,0,0,0)')
+    this.path2.setAttribute('stroke', this.stroke)
+    this.path2.setAttribute('fill', 'rgba(0,0,0,0)')
+
+    this.g.appendChild(this.path1)
+    this.g.appendChild(this.path2)
+
+    node.insertBefore(this.g, node.children[0])
+
+    return node
+  }
+
+  update() {
+    super.update()
+    let gCenter = faceCenter(this.points)
+
+    let ydis = lineDis({points: [this.points[1], this.points[2]]})  / this.multiple
+    let xdis = lineDis({points: [this.points[2], this.points[3]]})  / this.multiple
+    let xscale = (xdis - this.width * 5) / xdis
+    let yscale = (ydis - this.width * 5) / ydis
+
+    xscale = xscale <= 0 ? 1: xscale
+    yscale = yscale <= 0 ? 1: yscale
+
+    this.g.setAttribute('transform', `translate(${gCenter.x},${gCenter.y}) scale(${xscale < yscale ? yscale : xscale}) translate(${-gCenter.x},${-gCenter.y})`);
+
+    this.path1.setAttribute('stroke-width', (this.width * this.multiple / (xscale < yscale ? yscale : xscale)).toString())
+    this.path1.setAttribute('d', `M ${this.points.map(({x, y}) => x + ' ' + y).join(' L ')} Z`)
+    this.path2.setAttribute('stroke', this.stroke)
+
+    let consult = this.points[2]
+    let line = {points: [this.points[1], this.points[3]]} as Line
+    let center = lineCenter(line)
+    let dis = lineDis({points: [center, consult]}) * (1 / 3)
+    // debugger
+    let [p1, p2] = getDisVerticalLinePoints(line, center, dis)
+    let point = lineDis({points: [consult, p1]}) > lineDis({points: [consult, p2]}) ? p2: p1
+
+    if (!pointInside(this.points, point)) {
+      point = lineCenter(line)
+    }
+
+    this.path2.setAttribute('stroke', this.stroke)
+    this.path2.setAttribute('stroke-width', (this.width * this.multiple).toString())
+    this.path2.setAttribute('d', `M ${this.points[1].x} ${this.points[1].y} L ${point.x} ${point.y} L ${this.points[3].x} ${this.points[3].y}`)
+  }
+}
+
+export default Flue

+ 325 - 0
src/CAD/core/furniture/furn.ts

@@ -0,0 +1,325 @@
+import {CADElement, ElementProps} from '../core/element'
+import Point from '../core/point'
+import {_room as Room} from '../base/processing/index'
+import {
+  lineDis,
+  Point as GeoPoint,
+  isFaceIntersect,
+  isFaceContain,
+  getLinePoint,
+  isFaceChild,
+  faceRotate,
+  pointInside
+} from '../geometry'
+import {type} from '../util'
+import { SVGURI } from '../constant/Element'
+import Line from '../core/line'
+
+export interface FurnProps extends ElementProps {
+  points: Array<Point>,
+  lines: Array<Line>,
+  fill: string,
+  angle: number,
+  width?: number,
+  stroke?: string
+}
+export interface seftProps extends ElementProps {
+  minWidth?: number
+  points: Array<GeoPoint>
+  top: number,
+  bottom: number,
+  angle: number,
+  width?: number,
+  stroke?: string
+}
+
+class RoomFurn extends CADElement<FurnProps> {
+  // 当前建筑线条关联得房间
+  top: number
+  bottom: number
+  wrapPath: SVGPathElement
+  initPoints: Array<GeoPoint>
+  stop: boolean
+  moveLineDis: number
+  movePoints: Array<Point>
+
+
+  constructor({minWidth = 0.1, points, renderer, top, bottom, angle = 0, stroke = 'rgba(255,255,255,0.5)', width = 1, ...args}: seftProps) {
+    let mPoints = points.map(point => new Point({...point, renderer: renderer}))
+    let mLines = mPoints.map((p, i) =>
+      new Line({
+        points: [
+          mPoints[i], 
+          mPoints[i === mPoints.length - 1 ? 0 : i +1]
+        ], 
+        renderer: renderer, 
+        width: width,
+        color: stroke
+      })
+    )
+
+    super({...args, points: mPoints, lines: mLines, renderer, angle, stroke, width, fill: 'rgba(0,0,0,0)'})
+    this.paths = mLines
+    this.points = mPoints
+    // this.top = top
+    // this.bottom = bottom
+    this.zIndex = 1
+    this.minWidth = minWidth
+    this.stop = true
+
+    let movePoints = []
+    Object.defineProperty(this, 'movePoints', {
+      get() {
+        return movePoints
+      },
+      set(val) {
+        movePoints = val
+        if (val && val.length >= 2) {
+          this.moveLineDis = lineDis({points: movePoints as [Point, Point]})
+        } else {
+          delete this.moveLineDis
+        }
+      }
+    })
+
+    this.real.setAttribute('class', 'variable')
+    
+
+    Object.defineProperty(this, 'top', {
+      get: () => top,
+      set(val) {
+        top = val
+        this.wallPos && this.wallPos()
+      }
+    })
+    
+    Object.defineProperty(this, 'bottom', {
+      get: () => bottom,
+      set(val) {
+        bottom = val
+        this.wallPos && this.wallPos()
+      }
+    })
+  }
+
+  setHoverStyle() {
+    this.fill = 'rgba(243, 255, 0, 0.8)'
+  }
+
+  setUnHoverStyle() {
+    this.fill = 'rgba(243, 255, 0, 0)'
+  }
+
+  rotate(angle) {
+    if (angle === this.angle) return false;
+    let points = faceRotate(this.points, angle - this.angle)
+
+    if (this.check(points)) {
+      this.stop = false
+      this.points.forEach((point, i) => {
+        point.x = points[i].x
+        point.y = points[i].y
+      })
+
+      this.nextTick(() => this.stop = true)
+      return true
+    } else {
+      return false
+    }
+  }
+
+  grentNode () {
+    let node = document.createElementNS(SVGURI, 'g')
+    this.wrapPath = document.createElementNS(SVGURI, 'path')
+    node.appendChild(this.wrapPath)
+
+    this.nextTick(() => {
+      this.lines.forEach(line => {
+        line.changeSelect = (isSelect) => {
+          isSelect && this.changeSelect(isSelect)
+        }
+        line.update()
+        node.appendChild(line.real)
+      })
+    })
+    return node
+  }
+
+  update() {
+    if (this.wrapPath) { 
+      this.wrapPath.setAttribute('stroke-width', (this.width * this.multiple).toString())
+      this.wrapPath.setAttribute('d', `M ${this.points.map(({x, y}) => x + ' ' + y).join(' L ')} z`)
+      this.wrapPath.setAttribute('fill', this.fill)
+      this.wrapPath.setAttribute('stroke', this.stroke)
+    }
+    this.lines.forEach(line => {
+      line.real.setAttribute('stroke', this.stroke)
+    })
+  }
+  // 宽度是否合格
+  checkWidth(points: Array<GeoPoint> = this.points) : boolean {
+    return !points.some((currentPoint, i) => {
+      let nextPoint = points[i === points.length - 1 ? 0 : i + 1]
+      return lineDis({points: [currentPoint, nextPoint]}) <= this.minWidth
+    })
+  }
+  
+  // 检测是否合格通过
+  qualified(points: Array<GeoPoint> = this.points, room = this.room) {
+    let roomEle = this.renderer.processing.getRoomEles(room)
+    
+    if (!isFaceContain(roomEle.ground, points)) {
+      return false
+    } else if (roomEle.hole.some(hole => 
+      isFaceIntersect(hole, points) || 
+      isFaceContain(hole, points) || 
+      isFaceContain(points, hole)
+    )) {
+      return false
+    } else {
+      return true
+    }
+  }
+
+  // 检查合格性
+  check(points: Array<GeoPoint> = this.points) {
+    return this.checkWidth(points) 
+  }
+
+  // 当变化时
+  intercept(trgs: Array<any>, {x, y, angle}, rets) {
+    if (type.isNumber(angle)) {
+      return this.rotate(angle)
+    }
+    return true;
+
+    if (type.isUndefined(x) || type.isUndefined(y) || !this.stop) return true;
+    
+
+    let seftPointIndex = trgs.findIndex(trg => this.points.some(p => p === trg))
+    if (!~seftPointIndex) return;
+    let moveLine = this.movePoints && this.movePoints.length &&
+      this.lines.find(line => ~line.points.indexOf(this.movePoints[0]) && ~line.points.indexOf(this.movePoints[1]));
+
+    moveLine = moveLine || this.lines.find(line => line.__draging || line.select)
+
+    if (!moveLine) return false;
+
+    let current = this.points.indexOf(trgs[seftPointIndex])
+    let points = [...this.points]
+    let consultLine = trgs.find(trg => trg !== moveLine && trg instanceof Line)
+    let currentIndex = this.lines.indexOf(moveLine)
+    let consultIndex = this.lines.indexOf(consultLine)
+    let otherIndex = 0
+
+    if (currentIndex + 1 === consultIndex) {
+      otherIndex = currentIndex - 1
+    } else if (currentIndex - 1 === consultIndex) {
+      otherIndex = currentIndex + 1
+    } else if (consultIndex === 0) {
+      otherIndex = currentIndex - 1
+    } else if (consultIndex === this.lines.length - 1) {
+      otherIndex = currentIndex + 1
+    }
+
+    if (otherIndex === this.lines.length) {
+      otherIndex = 0
+    } else if (otherIndex === -1) {
+      otherIndex = this.lines.length - 1
+    }
+
+    let otherLine = this.lines[otherIndex]
+    let otherLinePointIndex = Number(!moveLine.points.indexOf(trgs[seftPointIndex]))
+    let other = this.points.indexOf(moveLine.points[otherLinePointIndex])
+    let moveCurrent = getLinePoint(consultLine, {x, y})
+    let moveOther1 = getLinePoint(otherLine, {x, y})
+    let ox = points[other].x + x - trgs[seftPointIndex].x 
+    let oy = points[other].y + y - trgs[seftPointIndex].y 
+    let moveOther2 = getLinePoint(otherLine, {x: ox, y: oy})
+    let moveOther = Math.abs(this.moveLineDis - lineDis({points: [moveCurrent, moveOther1]})) > 
+      Math.abs(this.moveLineDis - lineDis({points: [moveCurrent, moveOther2]})) ?  
+      moveOther2 : moveOther1
+
+    points[current] = moveCurrent as Point
+    points[other] = moveOther as Point
+
+    if (this.check(points)) {
+        this.directUpdate(points)
+    }
+    return false
+
+    // return this.check(points) ? moveCurrent : false
+  }
+
+  directUpdate(points) {
+    points.forEach((point, i) => {
+      this.points[i].__state.direct = true
+      this.points[i].x = point.x
+      this.points[i].y = point.y
+      this.points[i].__state.direct = false
+      this.points[i].update()
+    })
+
+    this.lines.forEach(line => {
+      line.update()
+    })
+
+    this.update()
+    
+    this.wallPos && this.wallPos()
+    this.fromAsync && this.fromAsync()
+
+  }
+  
+  dragStart(ev) {
+    if (!this.lines.some(line => line.real === ev.target)) {
+      this.initPoints = this.points.map(point => ({
+        x: point.x,
+        y: point.y
+      }))
+    } else {
+      let dragLine = this.lines.find(line => line.real === ev.target)
+      this.moveLineDis = lineDis(dragLine)
+    }
+  }
+
+  drag(offset) {
+    if (!this.initPoints) return;
+    this.stop = false
+
+    let points = this.points.map((point, i) => ({
+      x: this.initPoints[i].x + offset.x * this.multiple,
+      y: this.initPoints[i].y + offset.y * this.multiple
+    }))
+    
+    setTimeout(() => {
+      this.directUpdate(points)
+    })
+  }
+
+  dragEnd() {
+    this.nextTick(() => {
+      this.stop = true
+      delete this.moveLineDis
+    })
+    delete this.initPoints
+  }
+
+
+  destroy() {
+    this.points.forEach(point => point.destroy())
+    this.room = null
+    this.points = null
+    this.update = null
+    super.destroy()
+  }
+}
+
+
+type RoomFurnTemp<T={}> = RoomFurn & T
+
+export type RoomFurnTs = ({ new <T={}, K = any>(data: T): RoomFurnTemp<T> }) & {
+  attaArch: Map<Room, Array<RoomFurn>>
+}
+export type FurnClass = RoomFurn
+export default RoomFurn as unknown as RoomFurnTs

+ 712 - 0
src/CAD/core/geometry.ts

@@ -0,0 +1,712 @@
+import { type } from './util'
+import { point } from './core/fixedpoint'
+
+export type Point = { x: number, y: number }
+export type Line = { points: [Point, Point] }
+
+export const calcVector = (point: Point) => {
+  point = { ...point }
+
+  let abs = point.x * point.y < 0 ? Math.abs(point.x - point.y) : point.x + point.y
+  let z = 1 / abs
+  point.x = point.x * z
+  point.y = point.y * z
+
+  return point
+}
+
+/**
+ * 计算直线向量
+ * @param {*} line 
+ */
+export const strictLineVector = (line: Line): Point => {
+  let cpoint = {
+    x: line.points[1].x - line.points[0].x,
+    y: line.points[1].y - line.points[0].y
+  }
+  let xd = Math.abs(cpoint.x / 1)
+  let yd = Math.abs(cpoint.y / 1)
+  let js = xd > yd ? xd : yd
+  let verctor = { x: cpoint.x / js, y: cpoint.y / js }
+  // verctor.x = Math.abs(verctor.x) < 0.01 ? 0 : verctor.x
+  // verctor.y = Math.abs(verctor.y) < 0.01 ? 0 : verctor.y
+
+  return calcVector(verctor)
+}
+
+/**
+ * 计算直线向量
+ * @param {*} line 
+ */
+export const lineVector = (line: Line, jq: number = 0.001): Point => {
+  let verctor = strictLineVector(line)
+
+  if (Math.abs(verctor.x) < jq) verctor.x = 0
+  if (Math.abs(verctor.y) < jq) verctor.y = 0
+
+  return verctor
+}
+
+/**
+ * 计算垂直向量
+ */
+export const verticalLine = (line: Line): Point => {
+  let { x, y } = lineVector(line)
+  if (x - y !== 0) {
+    let verctor = {
+      x: y / (y - x),
+      y: x / (x - y)
+    }
+    return calcVector(verctor)
+  } else {
+    return { x, y }
+  }
+}
+
+/**
+ * 计算直线长度
+ * @param line 
+ */
+export const lineDis = (line: Line): number => {
+  return Number(
+    Math.sqrt(
+      Math.pow(line.points[0].x - line.points[1].x, 2) +
+      Math.pow(line.points[0].y - line.points[1].y, 2)
+    ).toFixed(4)
+  )
+}
+
+
+/**
+ * 求线段的中心点坐标
+ * @param line 
+ */
+export const lineCenter = (line: Line): Point => ({
+  x: (line.points[0].x + line.points[1].x) / 2,
+  y: (line.points[0].y + line.points[1].y) / 2,
+})
+
+
+/**
+ * 求两线段交点坐标
+ * @param {*} line1 
+ * @param {*} line2 
+ */
+export const segmentsIntr = (line1: Line, line2: Line): Point | false => {
+  let [a, b] = line1.points
+  let [c, d] = line2.points
+  /** 1 解线性方程组, 求线段交点. **/
+  // 如果分母为0 则平行或共线, 不相交  
+  let denominator = (b.y - a.y) * (d.x - c.x) - (a.x - b.x) * (c.y - d.y);
+  if (denominator == 0) return false;
+
+  // 线段所在直线的交点坐标 (x , y)      
+  let x = ((b.x - a.x) * (d.x - c.x) * (c.y - a.y)
+    + (b.y - a.y) * (d.x - c.x) * a.x
+    - (d.y - c.y) * (b.x - a.x) * c.x) / denominator;
+  let y = -((b.y - a.y) * (d.y - c.y) * (c.x - a.x)
+    + (b.x - a.x) * (d.y - c.y) * a.y
+    - (d.x - c.x) * (b.y - a.y) * c.y) / denominator;
+
+  if (isContainPoint(line1, { x, y }) && isContainPoint(line2, { x, y })) {
+    return { x, y }
+  } else {
+    return false
+  }
+}
+
+
+/**
+ * 求两线段交点坐标,如果没有则延长
+ * @param {*} line1 
+ * @param {*} line2 
+ */
+export const segmentsIntrFine = (line1: Line, line2: Line): Point | false => {
+  let [a, b] = line1.points
+  let [c, d] = line2.points
+  /** 1 解线性方程组, 求线段交点. **/
+  // 如果分母为0 则平行或共线, 不相交  
+  let denominator = (b.y - a.y) * (d.x - c.x) - (a.x - b.x) * (c.y - d.y);
+  if (denominator == 0) return false;
+
+  // 线段所在直线的交点坐标 (x , y)      
+  let x = ((b.x - a.x) * (d.x - c.x) * (c.y - a.y)
+    + (b.y - a.y) * (d.x - c.x) * a.x
+    - (d.y - c.y) * (b.x - a.x) * c.x) / denominator;
+  let y = -((b.y - a.y) * (d.y - c.y) * (c.x - a.x)
+    + (b.x - a.x) * (d.y - c.y) * a.y
+    - (d.x - c.x) * (b.y - a.y) * c.y) / denominator;
+
+  if (isNaN(x) || isNaN(y)) {
+    return false
+  }
+
+  return { x, y }
+}
+
+/**
+ * 点到直线的距离
+ */
+export const pointLineDis = (line: Line, point: Point): number => {
+  let dis = 0
+  let s1 = line.points[1].x - line.points[0].x
+  let s2 = point.x - line.points[0].x
+  let s3 = point.x - line.points[1].x
+  let k1 = line.points[1].y - line.points[0].y
+  let k2 = point.y - line.points[0].y
+  let k3 = point.y - line.points[1].y
+  let cross = s1 * s2 + k1 * k2;
+  let d2 = s1 * s1 + k1 * k1;
+
+  if (cross <= 0) {
+    dis = Math.sqrt(s2 * s2 + k2 * k2);
+  } else if (cross >= d2) {
+    dis = Math.sqrt(s3 * s3 + k3 * k3);
+  } else {
+    let r = cross / d2;
+    let px = line.points[0].x + s1 * r;
+    let py = line.points[0].y + k1 * r;
+    dis = Math.sqrt((point.x - px) * (point.x - px) + (py - point.y) * (py - point.y));
+  }
+
+
+  return dis
+}
+
+/**
+ * 判断点是否在线上
+ * @param {*} point 要判断的点
+ * @param {*} line 线段
+ * @param {*} width 线宽
+ */
+export const isContainPoint = (line: Line, point: Point): boolean => {
+  return !Math.abs(pointLineDis(line, point)) || Math.abs(pointLineDis(line, point)) < 0.01
+}
+
+
+/**
+ * 获取一个指定X或Y在线段垂直中的点
+ * @param {*} line 哪条线
+ * @param {*} point X|Y数值
+ */
+export const getVerLinePoint = (line: Line, _point: { x: number } | { y: number } | Point): Point => {
+  return getVectorPosPoint(verticalLine(line), line.points[0], _point)
+}
+
+/**
+ * 获取一个指定X或Y在线段矢量中的点
+ * @param verctor 
+ * @param point 
+ * @param _point 
+ */
+export const getVectorPosPoint = (verctor: Point, point: Point, _point: { x: number } | { y: number } | Point): Point => {
+  let line = {
+    points: [
+      point,
+      {
+        x: point.x + verctor.x * 10000,
+        y: point.y + verctor.y * 10000
+      }
+    ]
+  } as Line
+
+  return getLinePoint(line, _point)
+}
+
+
+/**
+ * 获取一个指定X或Y在线段中的点
+ * @param {*} line 哪条线
+ * @param {*} point X|Y数值
+ */
+export const getLinePoint = (line: Line, _point: { x: number } | { y: number } | Point): Point => {
+  let point = _point as Point
+  if (type.isNumber(point.x) && type.isNumber(point.y)) {
+    let { x, y } = lineVector(line)
+    let attr = Math.abs(x) > Math.abs(y) ? 'x' : 'y'
+    point = { [attr]: point[attr] } as point
+  }
+  let newV = lineVector(line, 0.0001)
+  let k = newV.y / newV.x
+  let b = line.points[0].y - k * line.points[0].x
+
+  if (k > 100 || k < -100) return { x: line.points[0].x, y: point.y }
+
+  return type.isNumber(point.x) ?
+    { x: point.x, y: k * point.x + b } :
+    type.isNumber(point.y) ?
+      { x: (point.y - b) / k, y: point.y } :
+      { x: 0, y: 0 }
+}
+
+/**
+ * 获取点在线段固定X或Y位置
+ * @param line 哪条线
+ * @param point 
+ */
+export const getFlexLinePoint = (line: Line, point: Point): Point => {
+  let { x, y } = strictLineVector(line)
+  x = Math.abs(x)
+  y = Math.abs(y)
+
+  let move = x > y ? { x: point.x } : { y: point.y }
+
+  return getLinePoint(line, move)
+}
+
+/**
+ * 获取向量距离起点dis距离的点坐标
+ * @param {*} line 向量
+ * @param {*} startPoint 起点
+ * @param {*} dis 距离
+ * @returns [point, point] 位置点  两边延申所以有两个
+ */
+export const getDisVectorPoints = (lv: Point, startPoint: Point, dis: number): [Point, Point] => {
+  let F = Math.atan(lv.y / lv.x)
+  return [
+    {
+      x: startPoint.x + dis * Math.cos(F),
+      y: startPoint.y + dis * Math.sin(F),
+    }, {
+      x: startPoint.x + -dis * Math.cos(F),
+      y: startPoint.y + -dis * Math.sin(F),
+    }
+  ]
+}
+
+/**
+ * 获取直线距离指定起点dis距离的点坐标
+ * @param {*} line 直线
+ * @param {*} startPoint 起点
+ * @param {*} dis 距离
+ * @returns [point, point] 位置点  两边延申所以有两个
+ */
+export const getDisPointLinePoints = (line: Line, startPoint: Point, dis: number): [Point, Point] => {
+  return getDisVectorPoints(lineVector(line), startPoint, dis)
+}
+
+/**
+ * 获取直线距离起点dis距离的点坐标
+ * @param {*} line 直线
+ * @param {*} dis 距离
+ * @returns Point
+ */
+export const getLineDisPoint = (line: Line, dis: number): Point => {
+  let [p1, p2] = getDisPointLinePoints(line, line.points[0], dis)
+  let lv = lineVector(line)
+  let v1 = lineVector({ points: [line.points[0], p1] })
+
+  return lv.x * v1.x >= 0 && lv.y * v1.y >= 0 ? p1 : p2
+}
+
+
+/**
+ * 获取直线距离指定位置dis距离的点坐标
+ * @param {*} line 直线
+ * @param {*} dis 距离
+ * @returns Point
+ */
+export const getLineDisSelectPoint = (line: Line, point: Point, dis: number): Point => {
+  let [p1, p2] = getDisPointLinePoints(line, point, dis)
+
+  
+  if (pointLineDis(line, p1) < pointLineDis(line, p2)) {
+    return p1
+  } else {
+    return p2
+  }
+}
+
+/**
+ * 获取直线垂直线距离起点dis距离的点坐标
+ * @param {*} line 直线
+ * @param {*} startPoint 起点
+ * @param {*} dis 距离
+ */
+export const getDisVerticalLinePoints = (line: Line, startPoint: point, dis: number): [Point, Point] => getDisVectorPoints(verticalLine(line), startPoint, dis)
+
+/**
+ * 获取直线垂直距离交接点dis距离的点坐标
+ * @param line 直线
+ * @param vline 交界线
+ * @param dis 距离
+ */
+export const getLineVerticalChangePoint = (line: Line, vline: Line, dis: number): Point | false => {
+  let point = segmentsIntrFine(line, vline)
+  if (!point) {
+    return point
+  } else {
+    return getLineVerticalPoint(line, point, lineVector(vline), dis)
+  }
+}
+
+
+/**
+ * 获取直线垂直距离七点dis距离的点坐标,
+ * @param line 直线
+ * @param point 起点
+ * @param lv 垂直线矢量坐标
+ * @param dis 距离
+ */
+export const getLineVerticalPoint = (line: Line, point: Point, lv: point, dis: number): Point => {
+  let [p1, p2] = getDisVerticalLinePoints(line, point, dis)
+  let v1 = lineVector({ points: [point, p1] })
+  let v2 = lineVector({ points: [point, p2] });
+
+  let v1x = lv.x * v1.x
+  let v1y = lv.y * v1.y
+  let v2x = lv.x * v2.x
+  let v2y = lv.x * v2.y
+
+  if (v1x >= 0 && v1y >= 0) {
+    return p1
+  } else if (v2x >= 0 && v2y >= 0) {
+    return p2
+  } else {
+    let t1 = v1x < v1y ? v1x : v1y
+    let t2 = v2x < v2y ? v2x : v2y
+
+    if (t1 < t2) {
+      return p2
+    } else {
+      return p1
+    }
+  }
+}
+
+// 获取一个坐标在哪个象限
+export const getPointCoordinate = (point: Point): number => {
+  return point.x >= 0 && point.y >= 0 ? 1 :
+    point.x >= 0 && point.y <= 0 ? 2 :
+      point.x <= 0 && point.y <= 0 ? 3 :
+        point.x <= 0 && point.y >= 0 ? 4 : 0
+}
+
+
+
+// 获取一个坐标在哪个象限
+export const getLineCoordinate = (line: Line): number => {
+  let point = {
+    x: line.points[1].x - line.points[0].x,
+    y: line.points[1].y - line.points[0].y
+  }
+  if (point.x >= 0 && point.y >= 0) {
+    return 1
+  } else if (point.x >= 0 && point.y <= 0) {
+    return 4
+  } else if (point.x <= 0 && point.y <= 0) {
+    return 3
+  } else {
+    return 2
+  }
+}
+
+
+// 获取两个坐标象限差距
+export const getPointCoordDistance = (point1: Point, point2: Point): number => getPointCoordinate(point1) - getPointCoordinate(point2)
+
+
+
+
+/**
+ * 计算多边形的面积
+ * @param {*} points 
+ */
+export const faceArea = (points: Array<Point>) => {
+  let point_num = points.length
+
+  if (point_num < 3) {
+    return 0
+  }
+
+  let s = points[0].y * (points[point_num - 1].x - points[1].x);
+
+  for (let i = 1; i < point_num; ++i)
+    s += points[i].y * (points[i - 1].x - points[(i + 1) % point_num].x);
+
+  return Math.abs(s / 2);
+}
+
+
+/**
+ * 判断一个点是否在面上
+ * @param {*} face1 
+ * @param {*} face2 
+ */
+export const pointInside = (face: Array<Point>, point: point) => {
+  var inside = false;
+  var x = point.x,
+    y = point.y;
+
+  for (var i = 0, j = face.length - 1; i < face.length; j = i++) {
+    var xi = face[i].x,
+      yi = face[i].y;
+    var xj = face[j].x,
+      yj = face[j].y;
+
+    if (((yi > y) != (yj > y)) &&
+      (x <= (xj - xi) * (y - yi) / (yj - yi) + xi)) {
+      inside = !inside;
+    }
+  }
+
+  // if (!inside) {
+  //   inside = face.some((p, i) => {
+  //     let next = i === face.length - 1 ? 0 : i+1
+  //     return isContainPoint({points: [face[i], face[next]]}, point)
+  //   })
+  // }
+
+  return inside;
+}
+
+
+/**
+ * 判断一个点是否在面线上
+ * @param {*} face1 
+ * @param {*} face2 
+ */
+export const pointInsideBorder = (face: Array<Point>, point: point) => {
+  for (let i = 0; i < face.length; i++) {
+    let current = {
+      points: [
+        face[i],
+        face[i === face.length - 1 ? 0 : i + 1]
+      ]
+    } as Line
+
+    if (isContainPoint(current, point)) {
+      return true
+    }
+  }
+  return false
+}
+
+
+export const isClockWise = (points: Array<Point>, isYAxixToDown = true) => {
+  let i, j, k;
+  let count = 0;
+  let z;
+  let yTrans = isYAxixToDown ? (-1) : (1);
+  if (points == null || points.length < 3) {
+    return false;
+  }
+
+  let n = points.length;
+  for (i = 0; i < n; i++) {
+    j = (i + 1) % n;
+    k = (i + 2) % n;
+    z = (points[j].x - points[i].x) * (points[k].y * yTrans - points[j].y * yTrans);
+    z -= (points[j].y * yTrans - points[i].y * yTrans) * (points[k].x - points[j].x);
+    if (z < 0) {
+      count--;
+    } else if (z > 0) {
+      count++;
+    }
+  }
+  return count > 0
+}
+
+
+
+/**
+ * 判断两条线段是否相交
+ * @param {*} line1 
+ * @param {*} line2 
+ */
+export const isLineIntersect = (line1: Line, line2: Line): boolean => {
+  var a1 = line1.points[1].y - line1.points[0].y;
+  var b1 = line1.points[0].x - line1.points[1].x;
+  var c1 = a1 * line1.points[0].x + b1 * line1.points[0].y;
+  //转换成一般式: Ax+By = C
+  var a2 = line2.points[1].y - line2.points[0].y;
+  var b2 = line2.points[0].x - line2.points[1].x;
+  var c2 = a2 * line2.points[0].x + b2 * line2.points[0].y;
+  // 计算交点		
+  var d = a1 * b2 - a2 * b1;
+
+  // 当d==0时,两线平行
+  if (d == 0) {
+    return false;
+  } else {
+    var x = (b2 * c1 - b1 * c2) / d;
+    var y = (a1 * c2 - a2 * c1) / d;
+
+    // 检测交点是否在两条线段上
+    if ((isInBetween(line1.points[0].x, x, line1.points[1].x) || isInBetween(line1.points[0].y, y, line1.points[1].y)) &&
+      (isInBetween(line2.points[0].x, x, line2.points[1].x) || isInBetween(line2.points[0].y, y, line2.points[1].y))) {
+      return true;
+    }
+  }
+
+  function isInBetween(a, b, c) {
+    // 如果b几乎等于a或c,返回false.为了避免浮点运行时两值几乎相等,但存在相差0.00000...0001的这种情况出现使用下面方式进行避免
+
+    if (Math.abs(a - b) < 0.000001 || Math.abs(b - c) < 0.000001) {
+      return false;
+    }
+
+    return (a <= b && b <= c) || (c <= b && b <= a);
+  }
+
+  return false;
+}
+
+
+/**
+ * 判断两个面是否相交, 
+ * @param {*} face1 
+ * @param {*} face2 
+ */
+export const isFaceIntersect = (face1: Array<point>, face2: Array<point>): boolean => {
+  for (var i = 0; i < face1.length; i++) {
+    var next = i + 1 === face1.length ? 0 : i + 1
+    var line1: Line = { points: [face1[i], face1[next]] }
+
+    for (var j = 0; j < face2.length; j++) {
+      var next = j + 1 === face2.length ? 0 : j + 1
+      var line2: Line = { points: [face2[j], face2[next]] }
+      var isIntersect1 = isLineIntersect(line2, line1)
+      var isIntersect2 = isLineIntersect(line1, line2)
+
+      if (isIntersect1 && isIntersect2) {
+        return true
+      }
+    }
+  }
+  return false
+}
+
+
+/**
+ * 判断两个面是否包含
+ * @param {*} face1 大多边形
+ * @param {*} face2 小多边形
+ */
+export const isFaceContain = (face1: Array<point>, face2: Array<point>): boolean => {
+  return face2.every(point => pointInside(face1, point)) && !isFaceIntersect(face1, face2)
+}
+
+
+
+/**
+ * 判断两个面是否包含
+ * @param {*} face1 父
+ * @param {*} face2 子
+ */
+export const isFaceChild = (face1: Array<point>, face2: Array<point>): boolean => {
+  return face2.every(point => pointInside(face1, point) || pointInsideBorder(face1, point)) && !isFaceIntersect(face1, face2)
+}
+
+
+/**
+ * 计算多边形的中心点
+ * @param {*} points 
+ */
+export const faceCenter = (points: Array<point>): point => {
+  var x = 0.0;
+  var y = 0.0;
+  for (var i = 0; i < points.length; i++) {
+    x += points[i].x;
+    y += points[i].y;
+  }
+  x = x / points.length;
+  y = y / points.length;
+
+  return { x: x, y: y }
+}
+
+/**
+ * 旋转多边形
+ * @param points 多边形
+ * @param angle 旋转角度
+ */
+export const faceRotate = function (points: Array<point>, angle: number): Array<point> {
+  const THREE = window.THREE
+  let center = faceCenter(points)
+  //先移动到中心点
+  let centerMat = new THREE.Matrix3()
+  let toRotCenterMatrix = centerMat.translate(-center.x, -center.y);
+  //旋转  
+  let tmpRotMat = new THREE.Matrix3();
+  let rotMatrix = tmpRotMat.rotate(THREE.Math.degToRad(angle));
+
+  //再移动回去
+  let backMat = new THREE.Matrix3();
+  let toOriPosMatrix = backMat.translate(center.x, center.y);
+
+  let matrix = new THREE.Matrix3();
+  matrix.premultiply(toRotCenterMatrix);
+  matrix.premultiply(rotMatrix);
+  matrix.premultiply(toOriPosMatrix);    //按顺序乘好,得到最终矩阵matrix
+
+  //然后用matrix更改所有点
+  return points.map((point) => {
+    var p = new THREE.Vector2(point.x, point.y);
+    p.applyMatrix3(matrix);
+    return {
+      x: p.x,
+      y: p.y
+    }
+  })
+}
+
+// 直线向两端伸展
+export const lineStretch = (line: Line, val: number) => {
+  line = { ...line, points: line.points.map(point => ({ x: point.x, y: point.y })) as [Point, Point] }
+  let center = lineCenter(line)
+  let [p1, p2] = getDisPointLinePoints(line, center, val / 2)
+
+  if (lineDis({ points: [line.points[0], p1] }) > lineDis({ points: [line.points[0], p2] })) {
+    line.points[0].x = p2.x
+    line.points[0].y = p2.y
+    line.points[1].x = p1.x
+    line.points[1].y = p1.y
+  } else {
+    line.points[0].x = p1.x
+    line.points[0].y = p1.y
+    line.points[1].x = p2.x
+    line.points[1].y = p2.y
+  }
+
+  return line
+}
+
+export const lineDeg = (line: Line) => {
+  return Math.atan2(line.points[1].y - line.points[0].y, line.points[1].x - line.points[0].x) * 180 / Math.PI
+}
+
+//两条相交的线段的夹角,永远小于180度
+export const getAngle = function(line1: Line, line2: Line) {
+  let o, s, e, index
+  if (~(index = line1.points.indexOf(line2.points[0]))) {
+    o = line1.points[index]
+    s = line2.points[1]
+    e = line1.points[Number(!index)]
+  } else if (~(index = line1.points.indexOf(line2.points[1]))) {
+    o = line1.points[index]
+    s = line2.points[0]
+    e = line1.points[Number(!index)]
+  }
+  var cosfi = 0, fi = 0, norm = 0;
+  var dsx = s.x - o.x;
+  var dsy = s.y - o.y;
+  var dex = e.x - o.x;
+  var dey = e.y - o.y;
+
+  cosfi = dsx * dex + dsy * dey;
+  norm = (dsx * dsx + dsy * dsy) * (dex * dex + dey * dey);
+  cosfi /= Math.sqrt(norm);
+
+  if (cosfi >= 1.0) return 0;
+  //if (cosfi <= -1.0) return Math.PI;  
+  if (cosfi <= -1.0) return 180;
+  fi = Math.acos(cosfi);
+
+  if (180 * fi / Math.PI < 180) {
+    return 180 * fi / Math.PI;
+  }
+  else {
+    return 360 - 180 * fi / Math.PI;
+  }
+}

+ 43 - 0
src/CAD/core/index.ts

@@ -0,0 +1,43 @@
+
+import CAD from './base/cad'
+import {insert, attachInsert} from './additional/insert'
+import {label, attachGauge} from './additional/label'
+import {sign, attachSign} from './additional/sign'
+import {Screenshot, attachScreenshot} from './additional/screenshot'
+import {transformRet, stackRet, autoPreservation, attchTransform, attchStack} from './additional/dataHandle'
+import {DOMTransform, attchDOMTranform} from './additional/transform'
+import {Disabled, attachDisabled} from './additional/disabled'
+import {Local, attachRote} from './additional/rote'
+import {Style, attachStyle} from './additional/styleSet'
+import { Data } from './base/processing/index'
+
+export type BasicCAD = CAD & insert & stackRet & label & DOMTransform & Screenshot & Disabled & Local & Style & sign
+export type AttachCAD = BasicCAD & transformRet
+export interface CADArgs {
+  data: Data,
+  layer: HTMLElement,
+  padding?: number
+}
+
+export function structureCAD({data, layer, padding = 40}: CADArgs): AttachCAD {
+  let cad = new CAD({ dom: layer, padding }) as AttachCAD
+
+  attachInsert(cad)
+  attchTransform(cad)
+  attchStack(cad)
+  autoPreservation(cad)
+  attchDOMTranform(cad)
+  attachDisabled(cad)
+  attachGauge(cad)
+  attachRote(cad)
+  attachScreenshot(cad)
+  attachSign(cad)
+  attachStyle(cad)
+
+  cad.preservation
+  cad.loadData(data)
+  cad.openMouseHandle()
+  cad.showGauge()
+  
+  return cad
+}

+ 179 - 0
src/CAD/core/label/direction.ts

@@ -0,0 +1,179 @@
+import {CADElement, ElementProps} from '../core/element'
+import {SVGURI} from '../constant/Element'
+import {getDisPointLinePoints, Line, lineDis} from '../geometry'
+import pan from './images/pan'
+import defaultIcon from './images/dire'
+import darkIcon from './images/dire_dark'
+
+let icon = {
+  defaultIcon, darkIcon
+}
+
+
+export interface StyleProps {
+  angle?: number
+  fontSize?: number,
+  r?: number
+  border?: number,
+  right?: number,
+  top?: number,
+  ArgDire?: number
+  simple?: boolean
+  icon?: string
+}
+
+export interface SeftProps extends StyleProps, ElementProps {}
+
+
+class Direction extends CADElement<SeftProps> {
+  dire: SVGImageElement
+  bg: SVGImageElement
+  topText: SVGTextElement
+  rightText: SVGTextElement
+  leftText: SVGTextElement
+  inner: SVGCircleElement
+  bottomText: SVGTextElement
+  arc: SVGPathElement
+
+  constructor({fontSize = 8, r = 50, icon = 'defaultIcon', border = 3, right = 20, top = 20, ArgDire = 15, simple = false, angle = 0, ...args} : SeftProps) {
+    super({fontSize, r, border, right, top, ArgDire, simple, icon, angle, ...args})
+  }
+
+  grentNode() {
+    const node = document.createElementNS(SVGURI, 'g')
+
+    
+    this.dire = document.createElementNS(SVGURI, 'image')
+    
+    this.bg = document.createElementNS(SVGURI, 'image')
+    this.bg.href.baseVal = pan
+
+    
+    this.inner = document.createElementNS(SVGURI, 'circle')
+    this.inner.setAttribute('fill', 'rgba(41,41,41,1)')
+    
+    let text = document.createElementNS(SVGURI, 'text')
+    text.setAttribute('fill', '#fff')
+    text.setAttribute('text-anchor', 'middle')
+    // this.titleNode.setAttribute('dy', '.4em')
+
+
+    this.topText = text.cloneNode(true) as SVGTextElement
+    this.rightText = text.cloneNode(true) as SVGTextElement
+    this.leftText = text.cloneNode(true) as SVGTextElement
+    this.bottomText = text.cloneNode(true) as SVGTextElement
+
+    this.topText.textContent = 'N'
+    this.topText.setAttribute('fill', '#00A0E9')
+    this.rightText.textContent = 'E'
+    this.leftText.textContent = 'W'
+    this.bottomText.textContent = 'S'
+    this.bottomText.setAttribute('fill', '#D84141')
+
+    this.arc = document.createElementNS(SVGURI, 'path')
+    this.arc.setAttribute('fill', 'rgba(255,255,255,0.3)')
+
+    node.appendChild(this.inner)
+    node.appendChild(this.arc)
+    node.appendChild(this.bg)
+    node.appendChild(this.dire)
+    node.appendChild(this.topText)
+    node.appendChild(this.rightText)
+    node.appendChild(this.bottomText)
+    node.appendChild(this.leftText)
+    return node
+  }
+
+  intercept() {
+    return true
+  }
+
+  update() {
+    const top = this.r / 2 + this.border + this.right
+    const right = this.r / 2 + this.border + this.top
+    const render = this.renderer
+    const fontSize = this.fontSize * this.multiple
+    const border = this.border * this.multiple
+    const pos = render.screenToRealPoint({
+      x: render.props.width / render.props.multiple - right,
+      y: top
+    });
+    if (isNaN(pos.x) || isNaN(pos.y)) return;
+    const w = this.r * this.multiple
+    const r = (fontSize + border * 3 + w)/2
+
+    this.inner.setAttribute('r', r.toString())
+    this.inner.setAttribute('cx', pos.x.toString())
+    this.inner.setAttribute('cy', pos.y.toString())
+
+    
+    if (this.icon !== this.firstIcon) {
+      this.dire.href.baseVal = icon[this.icon]
+      this.firstIcon = this.icon
+    }
+
+
+    this.dire.setAttribute('x', (pos.x -w/2).toString())
+    this.dire.setAttribute('y', (pos.y -w/2).toString())
+    this.dire.setAttribute('width', (w).toString())
+    this.dire.setAttribute('height', (w).toString())
+
+    this.bg.setAttribute('x', (pos.x -w/2).toString())
+    this.bg.setAttribute('y', (pos.y -w/2).toString())
+    this.bg.setAttribute('width', (w).toString())
+    this.bg.setAttribute('height', (w).toString())
+
+    this.topText.setAttribute('x', (pos.x ).toString())
+    this.topText.setAttribute('y', (pos.y - w/2 - fontSize / 2 + border).toString())
+    this.topText.setAttribute('font-size', fontSize.toString())
+    this.rightText.setAttribute('x', (pos.x + w/2 + border ).toString())
+    this.rightText.setAttribute('y', (pos.y + fontSize / 2).toString())
+    this.rightText.setAttribute('font-size', fontSize.toString())
+    this.bottomText.setAttribute('x', (pos.x).toString())
+    this.bottomText.setAttribute('y', (pos.y + w/2 + fontSize ).toString())
+    this.bottomText.setAttribute('font-size', fontSize.toString())
+    this.leftText.setAttribute('x', (pos.x - w/2 - fontSize + border).toString())
+    this.leftText.setAttribute('y', (pos.y + border).toString())
+    this.leftText.setAttribute('font-size', fontSize.toString())
+
+    const maxArg = this.ArgDire * this.multiple
+    const line1 = { points: [ 
+      pos,
+      {
+        x: pos.x - maxArg,
+        y: pos.y - w / 2 -fontSize - border
+      }
+    ]} as Line
+    const line2 = { points: [
+      pos, 
+      {
+        x: pos.x + maxArg,
+        y: pos.y - w / 2 -fontSize - border
+      }
+    ]} as Line
+    const [p11, p12] = getDisPointLinePoints(line1, line1.points[0], r)
+    const [p21, p22] = getDisPointLinePoints(line2, line2.points[0], r)
+    const p1 = lineDis({points: [line1.points[1], p11]}) > lineDis({points: [line1.points[1], p12]}) ? p12 : p11
+    const p2 = lineDis({points: [line2.points[1], p21]}) > lineDis({points: [line2.points[1], p22]}) ? p22 : p21
+
+    this.arc.setAttribute('d', `M ${pos.x} ${pos.y} L ${p1.x} ${p1.y} A ${r} ${r} 0 0 1 ${p2.x} ${p2.y} Z`)
+
+    this.real.setAttribute('transform', `rotate(${this.angle || 0} ${pos.x} ${pos.y})`)
+
+    if (this.simple) {
+      this.leftText.style.display = 'none'
+      this.rightText.style.display = 'none'
+      this.bg.style.display = 'none'
+      this.inner.style.display = 'none'
+      this.arc.style.display = 'none'
+    } else {
+      this.leftText.style.display = 'inherit'
+      this.rightText.style.display = 'inherit'
+      this.bg.style.display = 'inherit'
+      this.inner.style.display = 'inherit'
+      this.arc.style.display = 'inherit'
+    }
+  }
+}
+
+export default Direction

+ 301 - 0
src/CAD/core/label/gauge.ts

@@ -0,0 +1,301 @@
+import CAD from '../base/CAD'
+import {CADElement, ElementProps} from '../core/element'
+import {SVGURI, SVGPATH} from '../constant/Element'
+import Point from '../core/point'
+import {
+  Point as GeoPoint, 
+  Line as GeoLine,
+  getDisVerticalLinePoints, 
+  lineDis,
+  lineVector
+} from '../geometry'
+
+
+export interface SeftProps extends ElementProps, PublicProps {
+  surround?: boolean
+}
+
+export interface PublicProps {
+  width?: number, 
+  minPX?: number,
+  showLeft?: boolean,
+  showRight?: boolean,
+  showBottom?: boolean,
+  showTop?: boolean,
+  stroke?: string
+}
+
+export interface Props extends UpdateProps {
+  cad: CAD
+}
+
+export interface UpdateProps extends PublicProps {
+  spacing?: number
+  padding?: number
+  surround?: boolean
+}
+
+let id = 0
+const lineBoth = (point1: GeoPoint, point2: GeoPoint, w: number, l: number, fontSize: number, dir: number, padding: number, dire: string, stroke: string) => {
+  let $g = document.createElementNS(SVGURI, 'g')
+  let line = {points: [point1, point2]} as GeoLine
+  let lid = `lineBoth${id++}`
+  let [p1, p2] = getDisVerticalLinePoints(line, point1, l );
+  let [p3, p4] = getDisVerticalLinePoints(line, point2, l );
+
+  let $l = document.createElementNS(SVGURI, 'path')
+  $g.appendChild($l)
+  $l.setAttribute('stroke', stroke)
+  $l.setAttribute('stroke-width', w.toString())
+  if (dire === 'top' || dire === 'right') {
+    $l.setAttribute('d', `M ${p1.x} ${p1.y} L ${p3.x} ${p3.y}`)
+  } else {
+    $l.setAttribute('d', `M ${p2.x} ${p2.y} L ${p4.x} ${p4.y}`)
+  }
+  $l.setAttribute('id', lid)
+  
+
+  let $v = $l.cloneNode(true) as SVGPathElement
+  $v.setAttribute('d', `M ${p1.x} ${p1.y} L ${p2.x} ${p2.y}`)
+  $g.appendChild($v)
+  
+  $v = $l.cloneNode(true) as SVGPathElement
+  $v.setAttribute('d', `M ${p3.x} ${p3.y} L ${p4.x} ${p4.y}`)
+  $g.appendChild($v)
+
+  let width = ((dir.toString().length) / 2.5) * fontSize 
+  let xOffset = dir / 2 - width
+  let $text = document.createElementNS(SVGURI, 'text')
+  $text.setAttribute('x', xOffset.toString())
+  $text.setAttribute('y', '0')
+  $text.setAttribute('style', 'fill: ' + stroke + '; font-size: ' + fontSize + 'px')
+
+
+  let $content = document.createElementNS(SVGURI, 'textPath')
+  $content.setAttributeNS(SVGPATH, 'xlink:href', '#' + lid)
+  $content.appendChild(document.createTextNode(dir + 'm'))
+  
+  $text.appendChild($content)
+
+  let lv = lineVector({points: [point1, point2]})
+  let $d1 = document.createElementNS(SVGURI, 'path')
+  $d1.setAttribute('stroke', stroke)
+  $d1.setAttribute('stroke-width', w.toString())
+  if (lv.x > lv.y) {
+    $d1.setAttribute('d', `M ${point1.x} ${point1.y} L ${point2.x  - xOffset - width * 2 - padding} ${point2.y}`)
+  } else {
+    $d1.setAttribute('d', `M ${point1.x} ${point1.y} L ${point2.x} ${point2.y - xOffset - width * 2 - padding}`)
+  }
+  let $d2 = $d1.cloneNode(true) as SVGPathElement
+  if (lv.x > lv.y) {
+    $d2.setAttribute('d', `M ${point2.x - xOffset + padding} ${point1.y} L ${point2.x} ${point2.y}`)
+  } else {
+    $d2.setAttribute('d', `M ${point1.x} ${point2.y - xOffset + padding } L ${point2.x} ${point2.y}`)
+  }
+  $g.appendChild($d2)
+  $g.appendChild($d1)
+
+  
+  $l.style.display = 'none'
+
+  $g.appendChild($text)
+
+  return $g
+}
+
+class Gauge extends CADElement<SeftProps> {
+  cad: CAD
+  padding: number
+  top: SVGGElement 
+  left: SVGGElement
+  right: SVGGElement
+  bottom: SVGGElement
+  spacing: number
+
+  constructor({
+    cad, 
+    padding = 10, 
+    spacing = 4, 
+    minPX = 50, 
+    width = 1, 
+    showBottom = true, 
+    showTop = true, 
+    showLeft = true, 
+    showRight = true, 
+    surround = false,
+    stroke = '#fff',
+    ...args
+  }: Props) {
+    super({
+      minPX,
+      width,
+      renderer: cad.processing.render,
+      showBottom,
+      showTop,
+      showLeft,
+      showRight,
+      surround,
+      stroke,
+      ...args
+    })
+    this.cad = cad
+    this.padding = padding
+    this.spacing = spacing
+
+    Object.defineProperty(this, 'points', {
+      get() {
+        return cad.processing.points
+      }
+    })
+    
+    this.update()
+  }
+
+  grentNode() {
+    this.top = document.createElementNS(SVGURI, 'g')
+    this.left = document.createElementNS(SVGURI, 'g')
+    this.right = document.createElementNS(SVGURI, 'g')
+    this.bottom = document.createElementNS(SVGURI, 'g')
+
+    let node = document.createElementNS(SVGURI, 'g')
+    node.appendChild(this.top)
+    node.appendChild(this.left)
+    node.appendChild(this.right)
+    node.appendChild(this.bottom)
+
+    return node
+  }
+
+  calcSplit() {
+    // 一条线段最小间距
+    const render = this.cad.processing.render
+    const minPX = this.minPX
+    const minXDire = lineDis({points: [
+      render.screenToRealPoint({x: 0, y: 0}),
+      render.screenToRealPoint({x: minPX, y: 0})
+    ]})
+    const minYDire = lineDis({points: [
+      render.screenToRealPoint({x: 0, y: 0}),
+      render.screenToRealPoint({x: 0, y: minPX})
+    ]})
+    const points = this.points.map(({ele}) => ele)
+    const left = [], top = [], right = [], bottom = []
+    const calcX = (data, sort, attr) => {
+      points.sort(sort)
+      points.forEach(point => 
+        (data.length === 0 || point[attr] > data[data.length - 1]) ?
+          data.push(point[attr]) :
+          (point[attr] < data[0] && data.unshift(point[attr]))
+      );
+
+      let min = attr === 'x' ? minXDire : minYDire
+      let temp = {x: 0, y: 0}
+      for (let i = 0; i < data.length - 2; i++) {
+        let p1 = {...temp, [attr]: data[i]}
+        let p2 = {...temp, [attr]: data[i + 1]}
+        if (lineDis({points: [p1, p2]}) < min) {
+          data.splice(i + 1, 1)
+          i--
+        }
+      }
+
+      if (data.length > 2) {
+        data.splice(data.length - 2, 2, data[data.length - 1])
+      }
+    }
+
+    calcX(left, (a, b) => a.x - b.x ? a.x - b.x : b.y - a.y, 'y')
+    calcX(right, (a, b) => b.x - a.x ? b.x - a.x : b.y - a.y, 'y')
+    calcX(top, (a, b) => a.y - b.y ? a.y - b.y : b.x - a.x, 'x')
+    calcX(bottom, (a, b) =>b.y - a.y ? b.y - a.y : b.x - a.x, 'x')
+
+    return {left, top, right, bottom}
+  }
+
+  update() {
+    if (this.points.length === 0 || this.cad.processing.render.props.width === 0) return;
+
+    const render = this.cad.processing.render
+    const fontSize = this.width * 10 * this.multiple
+    const {left, right, top, bottom} = this.calcSplit()
+    let ltpoint = render.screenToRealPoint({x: this.padding, y:  this.padding})
+    let rbpoint = render.screenToRealPoint({
+      x: render.props.width / render.props.multiple - this.padding,
+      y: (render.props.height / render.props.multiple - this.padding)
+    });
+
+    if (this.surround) {
+      const xs = this.points.map(p => p.ele.x)
+      const ys = this.points.map(p => p.ele.y)
+      const maxX = Math.max(...xs)
+      const minX = Math.min(...xs)
+      const maxY = Math.max(...ys)
+      const minY = Math.min(...ys)
+
+      if (Math.abs(ltpoint.x - minX) < Math.abs(ltpoint.y - minY) && Math.abs(rbpoint.x - maxX) - Math.abs(rbpoint.y - maxY)) {
+        rbpoint = {
+          x: rbpoint.x,
+          y: maxY + Math.abs(ltpoint.x - minX)
+        }
+        ltpoint = {
+          x: ltpoint.x,
+          y: minY - Math.abs(ltpoint.x - minX)
+        }
+      } else {
+        rbpoint = {
+          x: maxX + Math.abs(ltpoint.y - maxY),
+          y: rbpoint.y
+        }
+        ltpoint = {
+          x: minX - Math.abs(ltpoint.y - minY),
+          y: rbpoint.y
+        }
+      }
+    }
+    
+    const draw = (arr, pointFn, cb, dire) => {
+      for (let i = 0; i < arr.length - 1; i++) {
+        let x1 = i === 0 ? arr[i] : arr[i] + (this.spacing / 2) * this.multiple
+        let x2 = i + 1 === arr.length ? arr[i + 1] : arr[i + 1] - (this.spacing / 2) * this.multiple
+
+        cb(
+          lineBoth(
+            pointFn(x1),
+            pointFn(x2),
+            this.width * this.multiple,
+            this.width * 4 * this.multiple,
+            fontSize,
+            Number(Math.abs(arr[i + 1] - arr[i]).toFixed(2)),
+            10 * this.multiple,
+            dire,
+            this.stroke
+          )
+        )
+      }
+    }
+
+    // this.left.innerHTML = ''
+    // this.top.innerHTML = ''
+    // this.right.innerHTML = ''
+    // this.bottom.innerHTML = ''
+
+    let arr = [this.left, this.top, this.right, this.bottom]
+    arr.forEach(dom =>{ 
+      while (dom.childNodes.length) dom.removeChild(dom.childNodes[0]) 
+    })
+
+
+    this.showLeft && draw(left, y => ({x: ltpoint.x, y}), dom => this.left.appendChild(dom), 'left')
+    this.showBottom && draw(bottom, x => ({x, y: rbpoint.y}), dom => this.bottom.appendChild(dom), 'bottom')
+    this.showTop && draw(top, x => ({x, y: ltpoint.y}), dom => {
+      dom.querySelector('text').setAttribute('transform', `translate(0, ${fontSize / 1.2})`)
+      this.top.appendChild(dom)
+    }, 'top')
+    this.showRight && draw(right, y => ({x: rbpoint.x, y}), dom => {
+      dom.querySelector('text').setAttribute('transform', `translate(-${fontSize / 1.2}, 0)`)
+      this.right.appendChild(dom)
+    }, 'right')
+  }
+}
+
+export default Gauge

BIN
src/CAD/core/label/images/dire.png


File diff suppressed because it is too large
+ 1 - 0
src/CAD/core/label/images/dire.ts


BIN
src/CAD/core/label/images/dire_dark.png


File diff suppressed because it is too large
+ 1 - 0
src/CAD/core/label/images/dire_dark.ts


BIN
src/CAD/core/label/images/icon_feiji.png


BIN
src/CAD/core/label/images/pan.png


File diff suppressed because it is too large
+ 1 - 0
src/CAD/core/label/images/pan.ts


BIN
src/CAD/core/label/images/rote-ts.png


+ 279 - 0
src/CAD/core/label/route.ts

@@ -0,0 +1,279 @@
+import {CADElement, ElementProps} from '../core/element'
+import {SVGURI} from '../constant/Element'
+import {Point as BasicPoint, lineDis} from '../geometry'
+
+export interface Point extends BasicPoint {
+  rangn: boolean
+}
+
+export interface SeftProps extends ElementProps {
+  show?: boolean,
+  local: Array<Point>,
+  active?: Point | 0
+  dialog?: {
+    pos: Point,
+    items: Array<Point>
+  } | 0
+}
+
+const contains = (parent, child) => {
+  if (parent.contains) {
+    return parent.contains(child)
+  } else {
+    return Array.from(parent.childNodes).some(node => node === child)
+  }
+}
+
+class Route extends CADElement<SeftProps> {
+  path: SVGPathElement
+  items: Array<SVGGElement>
+  temp: SVGGElement
+  dialogLayer: SVGGElement
+  itemsLayout: SVGPathElement
+
+  constructor({local, renderer, active = 0, show = true} : SeftProps) {
+    super({ local, renderer, dialog: 0, active, show })
+    this.items = []
+    this.layerClick = () => this.dialog = 0
+    renderer.layer.addEventListener('click', this.layerClick)
+  }
+
+  grentImage(w) {
+    let img = document.createElementNS(SVGURI, 'image')
+    img.href.baseVal = require('./images/rote-ts.png')
+    img.setAttribute('x', (-w/2).toString())
+    img.setAttribute('y', (-w/2).toString())
+    img.setAttribute('width', (w).toString())
+    img.setAttribute('height', (w).toString())
+
+    return img
+  }
+
+  grentNode() {
+    let node = document.createElementNS(SVGURI, 'g')
+
+    this.path = document.createElementNS(SVGURI, 'path')
+    this.path.setAttribute('stroke', 'rgb(0, 200, 175)')
+    this.path.setAttribute('fill', 'transparent')
+
+
+    let circle = document.createElementNS(SVGURI, 'circle')
+    circle.setAttribute('fill', 'rgb(0, 200, 175)')
+    circle.setAttribute('stroke', '#fff')
+
+    let text = document.createElementNS(SVGURI, 'text')
+    text.setAttribute('fill', '#fff')
+    text.setAttribute('text-anchor', 'middle')
+    text.setAttribute('dy', '.4em')
+
+    this.temp = document.createElementNS(SVGURI, 'g')
+    this.temp.appendChild(circle)
+    this.temp.appendChild(text)
+    this.temp.style.cursor = 'pointer'
+
+    this.dialogLayer = document.createElementNS(SVGURI, 'g')
+    this.itemsLayout = document.createElementNS(SVGURI, 'path')
+
+    this.itemsLayout.setAttribute('fill', 'rgba(255,255,255,0.7)')
+
+    this.dialogLayer.appendChild(this.itemsLayout)
+
+    node.appendChild(this.path)
+    return node
+  }
+
+  update() {
+    const pointR = 9 * this.multiple
+    const pointS = 2 * this.multiple
+
+
+    this.real.style.display = this.show ? 'initial' : 'none'
+    this.path.setAttribute('stroke-width', (2 * this.multiple).toString())
+
+    if (this.local.length > 0) {
+      this.path.setAttribute('d', 'M ' + this.local.map(({x, y}) => x + ' ' + y).join(' L ') + '')
+    } else {
+      this.path.setAttribute('d', '')
+      if (this.dialog)
+        return this.dialog = 0
+    }
+    
+    this.items.forEach(item => {
+      try {
+        this.real.removeChild(item)
+      } catch { }
+    })
+    this.items = []
+    this.temp.style.display = this.show ? 'initial' : 'none'
+    
+
+
+    let diff = this.local.length - this.items.length
+    if (diff > 0) {
+      for (let i = 0; i < diff; i++) {
+        let newL = this.temp.cloneNode(true) as SVGGElement
+        this.real.appendChild(newL)
+        this.items.push(newL)
+      }
+    } else if (diff < 0) {
+      for (let i = 0; i > diff; i--) {
+        let delL = this.items.shift()
+        this.real.removeChild(delL)
+      }
+    }
+
+    let count = 0
+    for (let i = 0; i < this.local.length; i++) {
+      let circle = this.items[i].querySelector('circle')
+      let text = this.items[i].querySelector('text')
+
+      this.items[i].setAttribute('transform', `translate(${this.local[i].x},${this.local[i].y})`)
+      this.items[i].style.display = 'inherit'
+      circle.setAttribute('stroke-width', pointS.toString())
+      circle.setAttribute('r', pointR.toString())
+      text.setAttribute('font-size', (10 * this.multiple).toString())
+
+      if (this.local[i].rangn) {
+        circle.setAttribute('fill', '#666666')
+        this.items[i].appendChild(
+          this.grentImage(pointR + pointS)
+        )
+      } else {
+        text.textContent = (++count).toString()
+      }
+
+      if (this.local[i] === this.active) {
+        text.setAttribute('fill', 'rgb(250,250,0)')
+        circle.setAttribute('stroke', 'rgb(250,250,0)')
+        this.items[i].setAttribute('class', 'active')
+      }
+    }
+
+    let group = this.calcGroup()
+    for (let i = 0; i < group.length; i++) {
+      group[i].body.forEach(b => {
+        let index = this.local.indexOf(b)
+        this.items[index].style.display = 'none'
+      })
+    }
+
+    this.showDialog((pointR + pointS) * 2)
+  }
+
+
+  showDialog(pointWidth: number) {
+    if (this.dialog === 0) return;
+    for (let i = 0; i < this.dialogLayer.childNodes.length; i++) {
+      if (this.dialogLayer.childNodes[i] !== this.itemsLayout) {
+        this.dialogLayer.removeChild(this.dialogLayer.childNodes[i])
+        i--
+      }
+    }
+    this.itemsLayout.innerHTML = ''
+    
+    
+    let margin = 5 * this.multiple
+    let width = (pointWidth + margin) * this.dialog.items.length
+    let height = (pointWidth + margin * 2)
+    let x = (this.dialog.pos.x - width / 2)
+    let y = (this.dialog.pos.y - height - pointWidth)
+    let arrow = 8 * this.multiple
+
+    this.itemsLayout.setAttribute('d', `
+      M ${x} ${y + height}
+      A ${height / 2} ${height / 2} 0 1 1 ${x} ${y}
+      L ${x + width} ${y}
+      A ${height / 2} ${height / 2} 0 1 1 ${x + width} ${y + height}
+      L ${x + width / 2 + arrow} ${y + height}
+      L ${x + width / 2} ${y + height + arrow}
+      L ${x + width / 2 - arrow} ${y + height}
+      L ${x} ${y + height}
+    `)
+
+    const appendChild = (dom, i) => {
+      dom.setAttribute('transform', `translate(${x + (pointWidth + margin) * i},${y + (pointWidth / 2 + margin)})`)
+      dom.style.display = 'inline'
+      this.dialogLayer.appendChild(dom)
+    }
+
+    this.dialog.items.forEach((item, i) => {
+        appendChild(this.items[this.local.indexOf(item)], i + 1)
+    })
+    appendChild(
+      this.items[this.local.indexOf(this.dialog.pos)].cloneNode(true),
+      0
+    )
+  }
+
+  calcGroup() {
+    let r = 18 * this.multiple
+    let group : Array<{body: Array<Point>, head: Point}> = [] 
+
+    this.local.forEach(a => {
+      if (group.find(g => ~g.body.indexOf(a))) return;
+      let body = this.local.filter(b => {
+        return a !== b && lineDis({points: [a, b]}) < r
+      })
+      if (body.length > 0) {
+        group.push({
+          head: a,
+          body
+        })
+      }
+    })
+    
+    return group
+  }
+
+  notice() {
+    if (this.dialog !== 0) {
+      this.real.appendChild(this.dialogLayer)
+    } else if (contains(this.real, this.dialogLayer)){
+      this.real.removeChild(this.dialogLayer)
+    }
+  }
+
+  intercept(stick, {active}) {
+    if (!active) return true;
+    let group = this.calcGroup()
+    let activeG = group.find(({head, body}) => (head === active || ~body.indexOf(active)))
+
+    this.nextTick(() => {
+      this.dialog = activeG ? {
+        pos: activeG.head,
+        items: [...activeG.body]
+      } : 0
+    })
+
+    if (this.active && this.items[this.local.indexOf(this.active)]) {
+      let dom = this.items[this.local.indexOf(this.active)]
+      let circle = dom.querySelector('circle')
+      let text = dom.querySelector('text')
+
+      text.setAttribute('fill', '#fff')
+      circle.setAttribute('stroke', '#fff')
+      dom.setAttribute('class', 'active')
+    }
+    return true
+  }
+
+  setActive(active: Point) {
+    this.active = active
+  }
+
+  click(ev) {
+    ev.stopPropagation()
+    for (let i = 0; i < this.items.length; i++) {
+      if (contains(this.items[i], ev.target) || ev.target === this.items[i]) {
+        this.setActive(this.local[i])
+        break;
+      }
+    }
+  }
+
+  destroy() {
+    this.renderer.layer.removeEventListener('click', this.layerClick)
+  }
+}
+
+export default Route

+ 97 - 0
src/CAD/core/label/sign.ts

@@ -0,0 +1,97 @@
+import {CADElement, ElementProps} from '../core/element'
+import {SVGURI} from '../constant/Element'
+import {Point} from '../geometry'
+import Renderer from '../base/renderer'
+
+let newCount = 0
+export interface StyleProps {
+  border?: number,
+  r?: number,
+  show?: boolean,
+  color?: string
+}
+
+export interface SeftProps extends ElementProps  {
+  pos: Point,
+  dire: number
+}
+
+class Sign extends CADElement<SeftProps & StyleProps> {
+  inner: SVGCircleElement
+  arc: SVGPathElement
+  
+  static Setting = new Map<Renderer, StyleProps>()
+
+  constructor({border, r, show = true, color = 'rgb(0, 200, 175)', ...args} : SeftProps & StyleProps) {
+    border = border || Sign.Setting.get(args.renderer).border
+    r = r || Sign.Setting.get(args.renderer).r
+    newCount++
+    super({border, r, show, color, ...args})
+  }
+
+  grentNode() {
+    let node = document.createElementNS(SVGURI, 'g')
+
+    this.inner = document.createElementNS(SVGURI, 'circle')
+    this.inner.setAttribute('stroke', '#fff')
+
+    this.arc = document.createElementNS(SVGURI, 'path')
+    this.arc.setAttribute('fill', `url(#orange_red${newCount})`)
+
+    let $defs = document.createElementNS(SVGURI, 'defs')
+
+    let $gradient = document.createElementNS(SVGURI, 'linearGradient')
+    $gradient.setAttribute('id', 'orange_red' + newCount)
+    $gradient.setAttribute('x1', '0%')
+    $gradient.setAttribute('y1', '0%')
+    $gradient.setAttribute('x2', '100%')
+    $gradient.setAttribute('y2', '100%')
+
+    let $stop1 = document.createElementNS(SVGURI, 'stop')
+    $stop1.setAttribute('offset', '0%')
+
+    let $stop2 = document.createElementNS(SVGURI, 'stop')
+    $stop2.setAttribute('offset', '100%')
+
+    $gradient.appendChild($stop1)
+    $gradient.appendChild($stop2)
+    $defs.appendChild($gradient)
+    // `
+
+    node.appendChild(this.arc)
+    node.appendChild(this.inner)
+    node.appendChild($defs)
+    node.setAttribute('class', 'sign')
+
+    this.$stop1 = $stop1
+    this.$stop2 = $stop2
+    return node
+  }
+
+  update() {
+    this.inner.setAttribute('fill', this.color)
+    this.$stop1.setAttribute('style', 'stop-color:' + this.color + '; stop-opacity:1')
+    this.$stop2.setAttribute('style', 'stop-color:' + this.color + '; stop-opacity:0.4')
+    this.inner.setAttribute('r', (this.r * this.multiple).toString())
+    this.inner.setAttribute('stroke-width', (this.border * this.multiple).toString())
+    this.inner.setAttribute('cx', this.pos.x.toString())
+    this.inner.setAttribute('cy', this.pos.y.toString())
+
+    const minArg = 2 * this.multiple
+    const ctrArg = 8 * this.multiple
+    const maxArg = 14 * this.multiple
+
+    this.arc.setAttribute('d', `
+      M ${this.pos.x} ${this.pos.y - minArg} 
+      L ${this.pos.x + maxArg} ${this.pos.y - ctrArg} 
+      L ${this.pos.x + maxArg} ${this.pos.y + ctrArg} 
+      L ${this.pos.x} ${this.pos.y + minArg} Z
+    `)
+
+    this.real.setAttribute('transform', `rotate(${this.dire} ${this.pos.x} ${this.pos.y})`)
+
+    this.real.style.display = this.show ? 'inherit': 'none'
+  }
+}
+
+export default Sign

+ 129 - 0
src/CAD/core/label/tagging.ts

@@ -0,0 +1,129 @@
+import {CADElement, ElementProps} from '../core/element'
+import {SVGURI} from '../constant/Element'
+import {Point as dataPoint} from '../geometry'
+import Point from '../core/point'
+import {strEascpeLen} from '../util'
+import { i18n } from '../../../../lang/index'
+export interface BasicProps extends ElementProps {
+  title: string,
+  content: string,
+  fontSize?: number,
+  show: boolean,
+  showTitle?: boolean,
+  showContent?: boolean
+}
+
+export interface ParentProps extends BasicProps {
+  point: Point
+  color?: string
+}
+
+export interface SeftProps extends BasicProps {
+  pos: dataPoint,
+  color?: string
+}
+
+class Tagging extends CADElement<ParentProps> {
+  point: Point
+  inner: SVGCircleElement
+  arc: SVGPathElement
+  titleNode: SVGTextElement
+  contentNode: SVGTextElement
+
+  constructor({pos, renderer, fontSize = 12, color='#fff',...args}: SeftProps) {
+    let point = new Point({ 
+      x: pos.x,
+      y: pos.y,
+      renderer, 
+      r1: fontSize,
+      r: fontSize * args.title.length,
+      fillColor: 'rgba(0,0,0,0)',
+      hover: {
+        fillColor: 'rgba(255,255,255,0)'
+      }
+    })
+    super({...args, point, fontSize, renderer, color})
+    this.zIndex = -1
+
+    this.point.changeSelect = (...args) => {
+      this.changeSelect(...args)
+    }
+
+    renderer.push(this.point)
+  }
+
+  changeSelect(isSelect) {
+    super.changeSelect(isSelect)
+  }
+
+  grentNode() {
+    let node = document.createElementNS(SVGURI, 'g')
+
+    this.titleNode = document.createElementNS(SVGURI, 'text')
+    this.titleNode.setAttribute('fill', '#fff')
+    this.titleNode.setAttribute('text-anchor', 'middle')
+    this.titleNode.setAttribute('dy', '.4em')
+
+    this.contentNode = document.createElementNS(SVGURI, 'text')
+    this.contentNode.setAttribute('fill', '#fff')
+    this.contentNode.setAttribute('text-anchor', 'middle')
+    this.contentNode.setAttribute('dy', '.4em')
+
+    node.appendChild(this.titleNode)
+    node.appendChild(this.contentNode)
+
+    this.nextTick(() => this.update())
+
+    return node
+  }
+
+  update() {
+    let fontSize = this.fontSize * this.multiple
+
+    this.point.real.style.display = this.show ? 'inherit' : 'none'
+    
+    this.titleNode.setAttribute('x', this.point.x.toString())
+    this.titleNode.setAttribute('y', this.point.y.toString())
+    this.titleNode.setAttribute('font-size', fontSize.toString())
+    this.titleNode.style.display = this.show && this.showTitle ? 'inherit' : 'none'
+    this.titleNode.textContent = this.title || i18n.t('modules.model.tagging_name_tips')
+    
+    this.contentNode.setAttribute('x', this.point.x.toString())
+    this.contentNode.setAttribute('y', (this.point.y + fontSize).toString())
+    this.contentNode.setAttribute('font-size', fontSize.toString())
+    this.contentNode.style.display = this.show && this.showContent ? 'inherit' : 'none'
+    this.contentNode.textContent = this.content
+
+    if (this.select) {
+      this.titleNode.setAttribute('fill', 'rgb(0, 200, 175)')
+      this.contentNode.setAttribute('fill', 'rgb(0, 200, 175)')
+    } else  {
+      this.titleNode.setAttribute('fill', this.color)
+      this.contentNode.setAttribute('fill', this.color)
+    }
+  }
+
+  intercept (stick, {title, content}) {
+    if (title || content) {
+      title = title || this.title
+      content = content || this.content
+      
+      let width = Math.max(
+        strEascpeLen(title),
+        strEascpeLen(content)
+      ) / 4
+      
+      this.point.r = width * this.fontSize
+      this.point.r1 = this.fontSize
+    }
+    return true
+  }
+
+  destroy() {
+    super.destroy()    
+    this.point.destroy()
+    this.renderer.remove(this.point)
+  }
+}
+
+export default Tagging

+ 3 - 0
src/CAD/core/style.css

@@ -0,0 +1,3 @@
+.sign * {
+  /* transition: all .3s linear; */
+}

+ 103 - 0
src/CAD/core/util.ts

@@ -0,0 +1,103 @@
+// 获取一组数字的横向大小
+export function getRange(data: Array<number>): number {
+  let min = Math.abs(Math.min(...data))
+  let max = Math.abs(Math.max(...data))
+  let range = Math.ceil( min + max )
+
+  return range
+}
+
+// 获取当前图像与真实DOM转换比例以及转换后wh
+export function getMapMultipleWH(domWidth: number, domHeight: number, data: any, padding: number = 10) {
+  
+  let minX = Math.abs(Math.min(...data.vertex.map(p => p.x)))
+  let maxX = Math.abs(Math.max(...data.vertex.map(p => p.x)))
+  let minY = Math.abs(Math.min(...data.vertex.map(p => p.y)))
+  let maxY = Math.abs(Math.max(...data.vertex.map(p => p.y)))
+  let xRang = Math.ceil( minX + maxX )
+  let yRang = Math.ceil( minY + maxY )
+  let xMultiple = xRang / (domWidth)
+  let yMultiple = yRang / (domHeight)
+  let multiple = xMultiple > yMultiple ? xMultiple : yMultiple
+  let width = domWidth * multiple
+  let height = domHeight * multiple
+  let top = ((minY / (minY + maxY)) * height) 
+  let left = ((minX / (minX + maxX)) * width) 
+  let scale = 1 - (padding * 2) * multiple / width
+
+  return { width, height, multiple, left, top, scale }
+}
+
+// 防抖
+export const debounce = (fn: Function, mis: number = 16, handleArgs?: Function, firstHandle?: Function) => {
+  let time = null
+  let args = []
+  let count = 0
+
+  return (...arg) => {
+    count || (firstHandle && firstHandle());
+    count++
+    args.push(arg)
+    clearTimeout(time)
+    time = setTimeout(() => {
+      if (handleArgs) {
+        fn(handleArgs(args))
+        args = []
+      } else {
+        fn(...arg)
+      }
+      count = 0
+    }, mis)
+  }
+}
+
+// 节流
+export const throttle = (fn, gapTime) => {
+  let _lastTime = null;
+
+  return function (...args) {
+    let _nowTime = + new Date()
+    if (_nowTime - _lastTime > gapTime || !_lastTime) {
+      fn(...args);
+      _lastTime = _nowTime
+    }
+  }
+}
+
+// 获取类型
+export const getType = (val: any) : string => {
+  return Object.prototype.toString.call(val).slice(8, -1)
+}
+
+// 扩展方法
+export const type : { [key in string]: { (val: any) : boolean } } = {};
+
+let Types = ['String', 'Number', 'Boolean', 'Undefined', 'Null', 'Object', 'Function', 'Array', 'Date', 'RegExp']
+Types.forEach(v => type[`is${v}`] = val => getType(val) === v)
+
+
+/**
+ * 将以base64的图片url数据转换为Blob
+ * @param urlData
+ *        用url方式表示的base64图片数据
+ */
+export const convertBase64UrlToBlob = (urlData: string) => {
+  var arr = urlData.split(','), mime = arr[0].match(/:(.*?);/)[1],
+    bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n);
+  while (n--) {
+    u8arr[n] = bstr.charCodeAt(n);
+  }
+  return new Blob([u8arr], { type: mime });
+}
+
+// 获取字符串长度,1个中文=2个英文
+export const strEascpeLen = (str: string) =>{ 
+  let value = str;
+  let length = value.length;
+
+  for(let i = 0; i < length; i++){
+    let valueEscape = escape(value.substr(i,1)); //编码
+    ~valueEscape.indexOf('%u') && length++
+  }
+  return length;
+ }

+ 335 - 0
src/CAD/eleAttr.ts

@@ -0,0 +1,335 @@
+import {AttachCAD} from './core/index'
+import Column from './core/architecture/column'
+import Door from './core/architecture/door/index'
+import Casement from './core/architecture/casement'
+import Arch from './core/architecture/linearch'
+import WallLine from './core/core/wallline'
+import Point from './core/core/point'
+import {FurnClass as Furn} from './core/furniture/furn'
+
+import {
+  lineDis,
+  lineCenter,
+  getDisPointLinePoints,
+  segmentsIntrFine,
+  isContainPoint,
+  lineStretch
+} from './core/geometry'
+import processing from './core/base/processing/index'
+
+// 拦截arch门窗柱子墙线条添加方法, 添加额外属性
+export default (cad: AttachCAD) => {
+  let addCase = cad.processing.addCase
+  let addDoor = cad.processing.addDoor
+  let addColumn = cad.processing.addColumn
+  let addLine = cad.processing.addLine
+  let addSlideDoor = cad.processing.addSlideDoor
+  let addBayCase = cad.processing.addBayCase
+  let addGroundCase = cad.processing.addGroundCase
+  let addFurnColumn = cad.processing.addFurnColumn
+  let addFurnFlue = cad.processing.addFurnFlue
+
+  // 添加与宽度相关的属性
+  let addWidthAttr = (ele: Column | Door | Casement ) => {
+    Object.defineProperties(ele, {
+      // 修改宽度
+      ewidth: {
+        get: () => Number(lineDis({points: ele.linePoints}).toFixed(2)),
+        set: (val) => {
+          if (val <= 0 || val === ele.ewidth) return;
+          let center = lineCenter({points: ele.linePoints})
+          let [p1, p2] = getDisPointLinePoints(ele.attachment, center, val / 2)
+
+          if (lineDis({points: [ele.linePoints[0], p1]}) > lineDis({points: [ele.linePoints[0], p2]})) {
+            ele.linePoints[0].x = p2.x
+            ele.linePoints[0].y = p2.y
+            ele.linePoints[1].x = p1.x
+            ele.linePoints[1].y = p1.y
+          } else {
+            ele.linePoints[0].x = p1.x
+            ele.linePoints[0].y = p1.y
+            ele.linePoints[1].x = p2.x
+            ele.linePoints[1].y = p2.y
+          }
+        }
+      },
+      // 最大宽度
+      maxWidth: {
+        get: () => {
+          let archs = [ele.attachment] as any
+          let p1MinPoints: Array<{dis: number, point: Point}> = []
+          let p2MinPoints: Array<{dis: number, point: Point}> = []
+          let item = (arch: any, i, j) : {dis: number, point: Point} => {
+            let points = arch.linePoints || arch.points
+            return {
+              dis: lineDis({points: [ele.linePoints[i], points[j]]}), 
+              point: points[j]
+            }
+          }
+
+          archs.forEach(arch => {
+            if (arch !== ele) {
+              p1MinPoints.push(item(arch, 0, 0))
+              p1MinPoints.push(item(arch, 0, 1))
+              p2MinPoints.push(item(arch, 1, 0))
+              p2MinPoints.push(item(arch, 1, 1))
+            }
+          })
+
+          p1MinPoints.sort((a, b) => a.dis - b.dis)
+          p2MinPoints.sort((a, b) => a.dis - b.dis)
+          
+          let points = (p1MinPoints[0].dis > p2MinPoints[0].dis ?
+            [ele.linePoints[1], p2MinPoints[0].point] :
+            [ele.linePoints[0], p1MinPoints[0].point]) as [Point, Point];
+
+          
+          return Number((lineDis({points}) * 2 + lineDis({points: ele.linePoints})).toFixed(2))
+        }
+      },
+      minWidth: {
+        get: () => 0
+      }
+    })
+  }
+
+  
+  // 添加与宽度相关的属性
+  let addStartAttr = (ele: Column | Door | Casement ) => {
+    Object.defineProperties(ele, {
+      // 修改宽度
+      estart: {
+        get() {
+          return ele.start
+        },
+        set: (val) => {
+          if (val) {
+            cad.processing.doors.forEach(door => {
+              door.ele.start = false
+            })
+          }
+          ele.start = val
+
+          cad.preservation()
+        }
+      }
+    })
+  }
+  
+
+  // 为柱子添加厚度的属性
+  let addTickAttr = (ele: Column) => {
+    Object.defineProperties(ele, {
+      tick: {
+        get: () => Math.max(
+          Number(lineDis({points: [ele.points[0], ele.points[3]]}).toFixed(2)),
+          Number(lineDis({points: [ele.points[1], ele.points[2]]}).toFixed(2)),
+        ),
+        set: (val) => {
+          if (val <= 0 || val === ele.oldVal) return;
+          ele.oldVal = val
+          ele.thickness = val
+          ele.updatePeripheral()
+        }
+      },
+      maxTick: {
+        get() {
+          return 10;
+          
+          let line1 = {points: [ele.points[0], ele.points[3]]} as {points: [Point, Point]}
+          let line2 = {points: [ele.points[1], ele.points[2]]} as {points: [Point, Point]}
+          let lines = ele.attachment.wallLines
+          let j1points = []
+          let j2points = []
+
+          for (let i = 0; i < lines.length; i++) {
+            if (lines[i] === ele.attachment) continue;
+
+            let point1 = segmentsIntrFine(lines[i], line1)
+            let point2 = segmentsIntrFine(lines[i], line2)
+
+            if (point1 && isContainPoint(lines[i], point1 as unknown as Point)) {
+              j1points.push(point1)
+            }
+            if (point2 && isContainPoint(lines[i], point2 as unknown as Point)) {
+              j2points.push(point2)
+            }
+          }
+
+          j1points.sort((a, b) => lineDis({points: [ele.points[0], a]}) - lineDis({points: [ele.points[0], b]}))
+          j2points.sort((a, b) => lineDis({points: [ele.points[1], a]}) - lineDis({points: [ele.points[1], b]}))
+
+          let minp1len = 0;
+          for(let i = 0; i < j1points.length; i++) {
+            if ( isContainPoint({points: [ele.points[0], j1points[i]]}, ele.points[3]) ||
+            isContainPoint({points: [ele.points[0], ele.points[3]]}, j1points[i]) ) {
+              minp1len = lineDis({points: [ele.points[0], j1points[i]]})
+              break;
+            }
+          }
+          let minp2len = 0;
+          for(let i = 0; i < j2points.length; i++) {
+            if ( isContainPoint({points: [ele.points[1], j2points[i]]}, ele.points[2]) ||
+            isContainPoint({points: [ele.points[1], ele.points[2]]}, j2points[i]) ) {
+              minp2len = lineDis({points: [ele.points[1], j2points[i]]})
+              break;
+            }
+          }
+
+          let len = !minp1len ? minp2len : 
+            !minp2len ? minp1len :
+              minp1len < minp2len ? minp1len : minp2len
+
+
+          return Number((len).toFixed(2)) || 10
+        }
+      },
+      minTick: {
+        get: () => 0
+      }
+    })
+  }
+
+
+  cad.processing.addCase = function(...args) {
+    let ret = addCase.call(this, ...args)
+    addWidthAttr(ret.ele)
+    return ret
+  }
+  
+  cad.processing.addBayCase = function(...args) {
+    let ret = addBayCase.call(this, ...args)
+    addWidthAttr(ret.ele)
+    return ret
+  }
+  
+  cad.processing.addGroundCase = function(...args) {
+    let ret = addGroundCase.call(this, ...args)
+    addWidthAttr(ret.ele)
+    return ret
+  }
+
+  cad.processing.addDoor = function(...args) {
+    let ret = addDoor.call(this, ...args)
+    addWidthAttr(ret.ele)
+    addStartAttr(ret.ele)
+    return ret
+  }
+  
+  cad.processing.addSlideDoor = function(...args) {
+    let ret = addSlideDoor.call(this, ...args)
+    addWidthAttr(ret.ele)
+    return ret
+  }
+
+
+  cad.processing.addColumn = function(...args) {
+    let ret = addColumn.call(this, ...args)
+    addWidthAttr(ret.ele)
+    addTickAttr(ret.ele)
+    return ret
+  }
+
+  cad.processing.addLine = function(...args) {
+    let ret = addLine.call(this, ...args)
+    return ret
+  }
+
+
+
+  let addFurnWidthAttr = (ele: Furn) => {
+    Object.defineProperties(ele, {
+      // 修改宽度
+      ewidth: {
+        get: () => Number(lineDis({points: [ele.points[0], ele.points[1]]}).toFixed(2)),
+        set: (val) => {
+          if (val <= 0 || val === ele.ewidth) return;
+          let newLine1 = lineStretch( { points: [ele.points[0], ele.points[1]] }, val )
+          let newLine2 = lineStretch( { points: [ele.points[2], ele.points[3]] }, val )
+          let points = newLine1.points.concat(newLine2.points)
+
+          if (ele.check(points)) {
+            ele.directUpdate(points)
+          }
+        }
+      },
+      // 最大宽度
+      maxWidth: {
+        get: () => {
+          return 10
+        }
+      },
+      minWidth: {
+        get: () => 0
+      }
+    })
+  }
+
+  let addFurnTickAttr = (ele: Furn) => {
+    Object.defineProperties(ele, {
+      tick: {
+        get: () => {
+          return Number(lineDis({points: [ele.points[1], ele.points[2]]}).toFixed(2))
+        },
+        set: (val) => {
+          if (val <= 0 || val === ele.tick ) return;
+          let newLine1 = lineStretch( { points: [ele.points[0], ele.points[3]] }, val )
+          let newLine2 = lineStretch( { points: [ele.points[1], ele.points[2]] }, val )
+          let points = [newLine1.points[0], newLine2.points[0], newLine2.points[1], newLine1.points[1]]
+
+          if (ele.check(points)) {
+            ele.directUpdate(points)
+          }
+        }
+      },
+      // 最大宽度
+      maxTick: {
+        get: () => {
+          return 10
+        }
+      },
+      minTick: {
+        get: () => 0
+      }
+    })
+  }
+
+
+  let addFurnAngleAttr = (ele: Furn) => {
+    Object.defineProperties(ele, {
+      maxAngle: {
+        get: () => 360
+      },
+      minAngle: {
+        get: () => 0
+      }
+    })
+  }
+
+  
+  cad.processing.addFurnColumn = function(...args) {
+    let ret = addFurnColumn.call(this, ...args)
+    addFurnWidthAttr(ret.ele)
+    addFurnTickAttr(ret.ele)
+    addFurnAngleAttr(ret.ele)
+    return ret
+  }
+
+  cad.processing.addFurnFlue = function(...args) {
+    let ret = addFurnFlue.call(this, ...args)
+    addFurnWidthAttr(ret.ele)
+    addFurnTickAttr(ret.ele)
+    addFurnAngleAttr(ret.ele)
+    return ret
+  }
+
+
+  let destroy = cad.destroy
+  cad.destroy = function(...args) {
+    addCase = null
+    addDoor = null
+    addColumn = null
+    addLine = null
+    destroy.apply(this, args)
+  }
+}

+ 7 - 0
src/CAD/images.d.ts

@@ -0,0 +1,7 @@
+declare module '*.svg'
+declare module '*.png'
+declare module '*.jpg'
+declare module '*.jpeg'
+declare module '*.gif'
+declare module '*.bmp'
+declare module '*.tiff'

+ 94 - 0
src/CAD/index.ts

@@ -0,0 +1,94 @@
+//import 'core-js'
+import {BasicCAD as CADTS, AttachCAD} from './core/index'
+import CAD from './core/base/cad'
+import {attachInsert} from './core/additional/insert'
+import {attachGauge} from './core/additional/label'
+import {attachSign} from './core/additional/sign'
+import {attachScreenshot} from './core/additional/screenshot'
+import {autoPreservation, attchTransform, attchStack} from './core/additional/dataHandle'
+import {attchDOMTranform} from './core/additional/transform'
+import {attachDisabled} from './core/additional/disabled'
+import {attachRote} from './core/additional/rote'
+import {attachStyle} from './core/additional/styleSet'
+import eleAttr from './eleAttr'
+import {other, Other} from './other'
+import Column from './core/architecture/column'
+import Door from './core/architecture/door/index'
+import Casement from './core/architecture/casement'
+import { CADElement } from './core/core/element'
+import Rote from './core/label/route'
+import screenAdapt from './screenAdapt'
+import toCanvas from './toCanvas'
+
+{
+  const listen = CADElement.prototype.listen
+  CADElement.prototype.listen = function (...args) {
+    if (this.render.edit || this instanceof Rote) {
+      listen.call(this, ...args)
+    }
+  }
+  const addEvent = Column.prototype.addEvent
+  Column.prototype.addEvent = function (...args) {
+    this.render.edit && addEvent.call(this, ...args)
+  }
+}
+
+export type EXPCAD = CADTS & Other & {toCanvas: (cb: Function) => void}
+export interface Args {
+  data: Data,
+  layer: HTMLElement,
+  edit: boolean,
+  padding?: number
+}
+
+import Point from './core/core/fixedpoint'
+import { Data } from './core/base/processing'
+
+export function structureCAD({data, layer, edit = true, padding = 20}: Args): EXPCAD {
+  let cad = new CAD({ dom: layer, padding }) as AttachCAD
+  cad.processing.render.edit = edit
+
+
+  if (edit) {
+    attachInsert(cad)
+    attchTransform(cad)
+    attchStack(cad)
+    autoPreservation(cad)
+    attchDOMTranform(cad)
+    attachDisabled(cad)
+    eleAttr(cad)
+  } else {
+    attchTransform(cad)
+    attchDOMTranform(cad)
+  }
+
+  attachStyle(cad)
+  attachScreenshot(cad)
+  attachSign(cad)
+  attachGauge(cad)
+  attachRote(cad)
+
+  if (edit) {
+    cad.openMouseHandle()
+    cad.showGauge()
+  }
+
+  other(cad as any)
+  screenAdapt(cad as any)
+  
+  cad.loadData(data);
+  (cad as unknown as EXPCAD).toCanvas = (cb) => toCanvas((cad as unknown as EXPCAD), cb)
+
+
+  return cad as any
+}
+
+export default structureCAD
+export {
+  Column,
+  Door,
+  Casement
+}
+
+declare global { interface Window { structureCAD: any; THREE: any; test: Array<Point> } }
+window.structureCAD = structureCAD

+ 29 - 0
src/CAD/other.ts

@@ -0,0 +1,29 @@
+import {EXPCAD as CAD} from './index'
+
+
+
+export interface Other{
+  showLabel: () => void,
+  hideLabel: () => void
+}
+
+// 附加走过路径的label
+export const other = (cad: CAD) => {
+  let show = false
+  let addTagging = cad.processing.addTagging
+
+  cad.processing.addTagging = (args) => {
+    (args as any).show = show
+    return addTagging.call(cad.processing, args)
+  }
+
+  cad.hideLabel = () => {
+    show = false
+    cad.processing.taggings.forEach(t => t.ele.show = false)
+  }
+
+  cad.showLabel = () => {
+    show = true
+    cad.processing.taggings.forEach(t => t.ele.show = show)
+  }
+}

+ 290 - 0
src/CAD/screenAdapt.ts

@@ -0,0 +1,290 @@
+import {EXPCAD} from './index'
+import {roateDataY} from './core/additional/dataHandle'
+
+const setStyle = async (cad: EXPCAD, lineWidth) => {
+  const processing = cad.processing
+  const doors = processing.doors
+  const slideDoors = processing.slideDoors
+  const groundCases = processing.groundCases
+  const cases = processing.cases
+  const points = processing.points
+  const tags = processing.taggings
+  const bayCases = processing.bayCases
+  const fcArchs = [
+    ...processing.furnFlues, 
+    ...processing.furnColumns, 
+    ...processing.columns,
+  ]
+  const pwidthArchs = [
+    ...processing.lines
+  ]
+  const pchildPoints = [
+    ...cases,
+    ...doors
+  ]
+
+  const pros = []
+
+  pros.concat(tags.map(door => {
+    let fontSize = lineWidth * 3
+    door.ele.fontSize = fontSize < 12 ? 12 : fontSize
+    return new Promise(resolve => door.ele.nextTick(resolve))
+  }));
+
+
+  pros.concat(fcArchs.map(door => {
+    door.ele.width = lineWidth * 0.33
+    return new Promise(resolve => door.ele.nextTick(resolve))
+  }))
+
+  pros.concat(slideDoors.map(door => {
+    door.ele.foorWidth = lineWidth*2 / (door.ele.attachment.border ? 1 : 2)
+    door.ele.bwithin = lineWidth / 2 / (door.ele.attachment.border ? 1 : 2)
+    return new Promise(resolve => door.ele.nextTick(resolve))
+  }))
+
+  pros.concat(cases.map(door => {
+    door.ele.width = lineWidth /2 * (door.ele.attachment.border ? 1 : 2)
+    door.ele.stamWidth = lineWidth * 0.166 / (door.ele.attachment.border ? 1 : 2)
+    return new Promise(resolve => door.ele.nextTick(resolve))
+  }))
+  pros.concat(pwidthArchs.map(door => {
+    door.ele.width = lineWidth 
+    return new Promise(resolve => door.ele.nextTick(resolve))
+  }))
+  pros.concat(bayCases.map(door => {
+    door.ele.width = lineWidth * 0.33 / (door.ele.attachment.border ? 1 : 2)
+    return new Promise(resolve => door.ele.nextTick(resolve))
+  }))
+  pros.concat(groundCases.map(door => {
+    door.ele.width = lineWidth / 2 * (door.ele.attachment.border ? 1 : 2)
+    return new Promise(resolve => door.ele.nextTick(resolve))
+  }))
+  pros.concat(doors.map(door => {
+    door.ele.foorWidth = lineWidth * 2 / (door.ele.attachment.border ? 1 : 2)
+    door.ele.outWidth = lineWidth * 0.33 / (door.ele.attachment.border ? 1 : 2)
+    return new Promise(resolve => door.ele.nextTick(resolve))
+  }))
+  pros.concat(points.map(door => {
+    door.ele.r1 = lineWidth + 1
+    door.ele.r = lineWidth + 1
+    return new Promise(resolve => door.ele.nextTick(resolve))
+  }))
+  pros.concat(pchildPoints.map(door => {
+    door.ele.linePoints[0].r1 = lineWidth + 1
+    door.ele.linePoints[0].r = lineWidth + 1
+    door.ele.linePoints[1].r1 = lineWidth + 1
+    door.ele.linePoints[1].r = lineWidth + 1
+  }))
+
+  pros.push(
+    cad.setDireAttrs({
+      fontSize: 2.6 * lineWidth,
+      r: 16.6 * lineWidth,
+      border: lineWidth,
+      right: lineWidth * 6.3,
+      top: lineWidth * 6.3,
+      ArgDire: lineWidth * 5
+    })
+  )
+  await Promise.all(pros)
+}
+
+
+export default (cad: EXPCAD) => {
+  // 因为getData变了所以要给oldData给处理截图
+  const screenshot = cad.screenshot
+  cad.screenshot = async ({
+    width = 1920 * 3 / 2, 
+    height = 1080 * 3 / 2, 
+    primaryColor = '#fff',
+    bgColor = 'rgba(0,0,0,0)', 
+    data = roateDataY((cad as any).getData(true, true)), 
+    padding = 3 * 15 * 20 / 2, 
+    showGauge = true, 
+    lineWidth = 20 / 2,
+    showDire = false,
+    showTags = true,
+    showDoorStart = true
+  } = {}) => {
+    const lwidth = cad.processing.lines[0] ? cad.processing.lines[0].ele.width : 3
+    cad.processing.render.layer.style.opacity = '0'
+
+    cad.processing.lines.forEach(({ele: line}) => {
+      line.color = primaryColor
+    })
+
+    let attrs: any = [
+      {key: 'stroke', attr: ['doors', 'columns', 'bayCases', 'furnColumns', 'furnFlues'], oldVals: []},
+      {key: 'fill', attr: ['cases', 'groundCases'], oldVals: []},
+      {key: 'foorColor', attr: ['slideDoors'], oldVals: []},
+      {key: 'color', attr: ['taggings'], oldVals: []},
+      {key: 'icon', attr: ['doors'], oldVals: []}
+    ]
+
+    attrs.forEach(({key, attr: kattr, oldVals}) => {
+      kattr.forEach((attr) => {
+        oldVals.push(cad.processing[attr].map(({ele}) => ele[key]))
+        cad.processing[attr].forEach(({ele}) => {
+          if (key === 'icon') {
+            ele[key] = primaryColor !== '#fff' ? 'darkIcon' : 'defaultIcon' 
+          } else {
+            ele[key] = primaryColor
+          }
+        })
+      })
+    })
+    
+
+    let direShow = cad.direction.show
+    if (!cad.direction.show && showDire) {
+      cad.showDire()
+    } else if (!showDire) {
+      cad.hideDire()
+    }
+    
+    let tagShow = cad.processing.taggings[0] && cad.processing.taggings[0].ele.show
+    if (showTags && !tagShow) {
+      cad.processing.taggings.forEach(({ele: tag}) => tag.show = true);
+    }
+    cad.processing.attrs.forEach(key => {
+      cad.processing[key].forEach(obj => {
+        obj.ele.select && obj.ele.changeSelect(false)
+      })
+    })
+
+
+    let pointFill = cad.processing.points[0].ele.fillColor
+    let lineFill = cad.processing.lines[0].ele.init.color
+    await Promise.all(
+      cad.processing.points.map(({ele}) => {
+        ele.fillColor = 'rgba(0,0,0,0)'
+        return new Promise(r => ele.nextTick(r))
+      })
+    )
+
+    let clearPointArchs = [].concat(cad.processing.doors).concat(cad.processing.groundCases).concat(cad.processing.cases)
+
+    for(let i = 0; i < clearPointArchs.length; i++) {
+      let points = (clearPointArchs[i].ele.points || clearPointArchs[i].ele.linePoints)
+      if (points) {
+        await Promise.all(
+          points.map(point => {
+            point.fillColor = 'rgba(0,0,0,0)'
+            return new Promise(r => point.nextTick(r))
+          })
+        )
+      }
+    }
+
+    await setStyle(cad, lineWidth)
+    await cad.setGaugeModel(1)
+    
+    let oldDireIcon = cad.direction.icon
+    await cad.setDireAttrs({ simple: true, icon: primaryColor !== '#fff' ? 'darkIcon' : 'defaultIcon' })
+
+
+    let doorStarts = cad.processing.doors.map(door => door.ele.showStart)
+    
+    await Promise.all(
+      cad.processing.doors.map(door => {
+        door.ele.showStart = showDoorStart
+        return new Promise(r => door.ele.nextTick(r))
+      })
+    )
+
+    let oldGaugeColor = cad.gauge && cad.gauge.stroke
+    if (showGauge) {
+      await cad.setGaugeAttrs({
+        minPX: Math.max(width, height),
+        width: lineWidth / 2,
+        showBottom: false,
+        showRight: false,
+        padding: lineWidth * 3.3,
+        stroke: primaryColor
+      })
+    }
+    
+    
+
+    cad.processing.attrs.forEach(key => {
+      cad.processing[key].forEach(obj => {
+        obj.ele.update()
+      })
+    })
+
+    await new Promise(r => setTimeout(r, 100))
+
+    const ret = await screenshot.call(this, {width, height, bgColor, data, spadding: padding, showGauge, lineWidth})
+
+    if (showGauge) {
+      await cad.setGaugeAttrs({
+        minPX: 50,
+        width: 1,
+        showBottom: true,
+        showRight: true,
+        padding: 10,
+        stroke: oldGaugeColor
+      })
+    }
+
+    
+    attrs.forEach(({key, attr: kattr, oldVals}) => {
+      kattr.forEach((attr, index) => {
+        cad.processing[attr].forEach(({ele}, aindex) => {
+          ele[key] = oldVals[index][aindex]
+        })
+      })
+    })
+    
+
+    await cad.setDireAttrs({ simple: false, icon: oldDireIcon })
+    
+    for(let i = 0; i < clearPointArchs.length; i++) {
+      let points = (clearPointArchs[i].ele.points || clearPointArchs[i].ele.linePoints)
+      if (points) {
+        await Promise.all(
+          points.map(point => {
+            point.fillColor = pointFill
+            return new Promise(r => point.nextTick(r))
+          })
+        )
+        }
+    }
+
+    await setStyle(cad, lwidth)
+    await cad.setGaugeModel(0)
+
+    cad.processing.points.forEach(({ele}) => ele.fillColor = pointFill)
+    await Promise.all(
+      cad.processing.lines.map(({ele}) => {
+        return new Promise(r => {
+          ele.color = lineFill
+          ele.nextTick(r)
+        })
+      })
+    )
+
+
+    
+
+    if (direShow) {
+      await cad.showDire()
+    } else {
+      await cad.hideDire()
+    }
+    if (tagShow) {
+      cad.processing.taggings.forEach(({ele: tag}) => tag.show = true);
+    } else {
+      cad.processing.taggings.forEach(({ele: tag}) => tag.show = false);
+    }
+
+    cad.processing.doors.forEach((door, i) => {
+      door.ele.showStart = doorStarts[i]
+    })
+
+    cad.processing.render.layer.style.opacity = '1'
+
+    return ret
+  }
+}

+ 56 - 0
src/CAD/toCanvas.ts

@@ -0,0 +1,56 @@
+import { EXPCAD } from "./index";
+
+
+const toCanvas = async (cad: EXPCAD, cb?: Function) => {
+  if (!!(window as any).ActiveXObject || "ActiveXObject" in window) {
+    return;
+  }
+  
+  let $layer = cad.processing.render.layer
+  let $layout = cad.processing.render.g
+  let width = (cad.processing.render.layer.offsetWidth || parseInt(getComputedStyle(cad.processing.render.layer).width)) * 3
+  let height = (cad.processing.render.layer.offsetHeight || parseInt(getComputedStyle(cad.processing.render.layer).height)) * 3
+
+  $layer.style.visibility = 'hidden'
+
+  cad.processing.doors.forEach(door => {
+    door.ele.showStart = false
+  })
+  let doms = [
+    ...cad.processing.points,
+    ...cad.processing.lines,
+    ...cad.processing.doors,
+    ...cad.processing.columns,
+    ...cad.processing.cases,
+    ...cad.processing.bayCases,
+    ...cad.processing.slideDoors,
+    ...cad.processing.groundCases,
+    ...cad.processing.furnFlues,
+    ...cad.processing.furnColumns
+  ]
+  // debugger
+  if (!doms.length) return;
+
+  await Promise.all(doms.map(({ele}) => new Promise(r => ele.nextTick(r))))
+
+  let $sign = $layer.querySelector('.sign') as SVGGElement
+  await cad.hideSign()
+
+  let {file} = await cad.screenshot({width, height, bgColor:'rgba(0,0,0,0)', padding: cad.padding, showGauge: false, lineWidth: 2, showDire: false, showTags: false, showDoorStart: false})
+  $layer.style.backgroundImage = `url(${URL.createObjectURL(file)})`
+
+  for (let i = 0; i < $layout.children.length; i++) {
+    if ($layout.children[i] !== $sign) {
+      $layout.removeChild($layout.children[i--])
+    }
+  }
+
+  await cad.showSign()
+  $sign.style.display = 'block'
+  $layer.style.visibility = 'visible'
+
+  cb && cb()
+
+}
+
+export default toCanvas

File diff suppressed because it is too large
+ 3298 - 0
static/Mesh_floorplan(1).json


File diff suppressed because it is too large
+ 1 - 0
static/data.js


+ 646 - 0
static/data.json

@@ -0,0 +1,646 @@
+{
+  "vertex": [
+    {
+      "id": 0,
+      "x": -4.52,
+      "y": -4.03
+    },
+    {
+      "id": 1,
+      "x": -2.79,
+      "y": -4.03
+    },
+    {
+      "id": 3,
+      "x": -0.22,
+      "y": -2.91
+    },
+    {
+      "id": 5,
+      "x": 1.83,
+      "y": -2.66
+    },
+    {
+      "id": 6,
+      "x": 1.83,
+      "y": -2.84
+    },
+    {
+      "id": 7,
+      "x": 5.84,
+      "y": -2.84
+    },
+    {
+      "id": 8,
+      "x": 5.84,
+      "y": 3.13
+    },
+    {
+      "id": 9,
+      "x": 2.67,
+      "y": 3.13
+    },
+    {
+      "id": 10,
+      "x": 2.66,
+      "y": 3.63
+    },
+    {
+      "id": 11,
+      "x": 0.19,
+      "y": 3.63
+    },
+    {
+      "id": 12,
+      "x": 0.19,
+      "y": 3.13
+    },
+    {
+      "id": 13,
+      "x": -4.76,
+      "y": 3.13
+    },
+    {
+      "id": 14,
+      "x": -4.76,
+      "y": -1.12
+    },
+    {
+      "id": 17,
+      "x": -4.52,
+      "y": -1.12
+    },
+    {
+      "id": 33,
+      "x": -0.05,
+      "y": 3.13
+    },
+    {
+      "id": 35,
+      "x": 2.66,
+      "y": 3.13
+    },
+    {
+      "id": 38,
+      "x": 5.84,
+      "y": 0.7
+    },
+    {
+      "id": 39,
+      "x": 2.66,
+      "y": 0.7
+    },
+    {
+      "id": 45,
+      "x": -0.04,
+      "y": -0.42
+    },
+    {
+      "id": 48,
+      "x": -0.22,
+      "y": -2.66
+    },
+    {
+      "id": 54,
+      "x": -2.57,
+      "y": -1.13
+    },
+    {
+      "id": 57,
+      "x": -2.78,
+      "y": -2.88
+    },
+    {
+      "id": 63,
+      "x": -0.05,
+      "y": 0.7
+    },
+    {
+      "id": 75,
+      "x": 3.72,
+      "y": -0.51
+    },
+    {
+      "id": 83,
+      "x": 2.66,
+      "y": 3.13
+    },
+    {
+      "id": 84,
+      "x": 1.8,
+      "y": -0.46
+    },
+    {
+      "id": 90,
+      "x": 3.72,
+      "y": 0.7
+    },
+    {
+      "id": 101,
+      "x": -2.57,
+      "y": -2.88
+    },
+    {
+      "id": 102,
+      "x": -0.07,
+      "y": -2.66
+    }
+  ],
+  "wall": [
+    {
+      "id": 1,
+      "p1": 0,
+      "p2": 1,
+      "border": true
+    },
+    {
+      "id": 2,
+      "p1": 57,
+      "p2": 3,
+      "border": true
+    },
+    {
+      "id": 3,
+      "p1": 3,
+      "p2": 48,
+      "border": true
+    },
+    {
+      "id": 4,
+      "p1": 5,
+      "p2": 6,
+      "border": true
+    },
+    {
+      "id": 5,
+      "p1": 6,
+      "p2": 7,
+      "border": true
+    },
+    {
+      "id": 6,
+      "p1": 38,
+      "p2": 8,
+      "border": true
+    },
+    {
+      "id": 7,
+      "p1": 35,
+      "p2": 9,
+      "border": true
+    },
+    {
+      "id": 8,
+      "p1": 35,
+      "p2": 10,
+      "border": true
+    },
+    {
+      "id": 9,
+      "p1": 10,
+      "p2": 11,
+      "border": true
+    },
+    {
+      "id": 10,
+      "p1": 11,
+      "p2": 12,
+      "border": true
+    },
+    {
+      "id": 11,
+      "p1": 33,
+      "p2": 13,
+      "border": true
+    },
+    {
+      "id": 12,
+      "p1": 13,
+      "p2": 14,
+      "border": true
+    },
+    {
+      "id": 13,
+      "p1": 17,
+      "p2": 0,
+      "border": true
+    },
+    {
+      "id": 14,
+      "p1": 63,
+      "p2": 33,
+      "border": false
+    },
+    {
+      "id": 15,
+      "p1": 39,
+      "p2": 35,
+      "border": false
+    },
+    {
+      "id": 16,
+      "p1": 39,
+      "p2": 63,
+      "border": false
+    },
+    {
+      "id": 17,
+      "p1": 48,
+      "p2": 5,
+      "border": true
+    },
+    {
+      "id": 18,
+      "p1": 7,
+      "p2": 38,
+      "border": true
+    },
+    {
+      "id": 19,
+      "p1": 8,
+      "p2": 35,
+      "border": true
+    },
+    {
+      "id": 20,
+      "p1": 12,
+      "p2": 33,
+      "border": true
+    },
+    {
+      "id": 21,
+      "p1": 84,
+      "p2": 45,
+      "border": false
+    },
+    {
+      "id": 22,
+      "p1": 102,
+      "p2": 45,
+      "border": false
+    },
+    {
+      "id": 23,
+      "p1": 90,
+      "p2": 39,
+      "border": false
+    },
+    {
+      "id": 24,
+      "p1": 83,
+      "p2": 9,
+      "border": true
+    },
+    {
+      "id": 25,
+      "p1": 83,
+      "p2": 35,
+      "border": true
+    },
+    {
+      "id": 26,
+      "p1": 75,
+      "p2": 90,
+      "border": false
+    },
+    {
+      "id": 27,
+      "p1": 38,
+      "p2": 90,
+      "border": false
+    },
+    {
+      "id": 28,
+      "p1": 5,
+      "p2": 84,
+      "border": false
+    },
+    {
+      "id": 29,
+      "p1": 54,
+      "p2": 17,
+      "border": false
+    },
+    {
+      "id": 30,
+      "p1": 101,
+      "p2": 54,
+      "border": false
+    },
+    {
+      "id": 31,
+      "p1": 14,
+      "p2": 17,
+      "border": true
+    },
+    {
+      "id": 32,
+      "p1": 1,
+      "p2": 57,
+      "border": true
+    },
+    {
+      "id": 33,
+      "p1": 101,
+      "p2": 57,
+      "border": false
+    },
+    {
+      "id": 34,
+      "p1": 102,
+      "p2": 48,
+      "border": false
+    },
+    {
+      "id": 35,
+      "p1": 75,
+      "p2": 84,
+      "border": false
+    }
+  ],
+  "window": [
+    {
+      "line": 11,
+      "pos": [
+        -0.56,
+        3.13,
+        -2.42,
+        3.13
+      ],
+      "top": null,
+      "bottom": null
+    },
+    {
+      "line": 9,
+      "pos": [
+        1.95,
+        3.63,
+        0.61,
+        3.63
+      ],
+      "top": null,
+      "bottom": null
+    },
+    {
+      "line": 6,
+      "pos": [
+        5.84,
+        1.14,
+        5.84,
+        2.63
+      ],
+      "top": null,
+      "bottom": null
+    },
+    {
+      "line": 18,
+      "pos": [
+        5.84,
+        -2.47,
+        5.84,
+        -0.74
+      ],
+      "top": null,
+      "bottom": null
+    },
+    {
+      "line": 5,
+      "pos": [
+        3.36,
+        -2.84,
+        2.49,
+        -2.84
+      ],
+      "top": null,
+      "bottom": null
+    },
+    {
+      "line": 17,
+      "pos": [
+        1.43,
+        -2.66,
+        0.32,
+        -2.66
+      ],
+      "top": null,
+      "bottom": null
+    },
+    {
+      "line": 32,
+      "pos": [
+        -2.79,
+        -3.66,
+        -2.78,
+        -3.05
+      ],
+      "top": null,
+      "bottom": null
+    }
+  ],
+  "door": [
+    {
+      "line": 23,
+      "pos": [
+        3.56,
+        0.7,
+        2.76,
+        0.7
+      ],
+      "top": null,
+      "bottom": null,
+      "within": 0,
+      "show": true,
+      "ctl": [
+        3.56,
+        1.5
+      ],
+      "start": false
+    },
+    {
+      "line": 12,
+      "pos": [
+        -4.76,
+        -0.74,
+        -4.76,
+        0.06
+      ],
+      "top": null,
+      "bottom": null,
+      "within": 0,
+      "show": true,
+      "ctl": [
+        -3.96,
+        -0.74
+      ],
+      "start": true
+    },
+    {
+      "line": 21,
+      "pos": [
+        0.85,
+        -0.44,
+        0.14,
+        -0.43
+      ],
+      "top": null,
+      "bottom": null,
+      "within": 0,
+      "show": true,
+      "ctl": [
+        0.8654333101400841,
+        0.26993226644386287
+      ],
+      "start": false
+    },
+    {
+      "line": 16,
+      "pos": [
+        2.32,
+        0.7,
+        1.52,
+        0.7
+      ],
+      "top": null,
+      "bottom": null,
+      "within": 0,
+      "show": true,
+      "ctl": [
+        2.32,
+        1.5
+      ],
+      "start": false
+    },
+    {
+      "line": 26,
+      "pos": [
+        3.72,
+        -0.32,
+        3.72,
+        0.46
+      ],
+      "top": null,
+      "bottom": null,
+      "within": 0,
+      "show": true,
+      "ctl": [
+        4.5,
+        -0.32
+      ],
+      "start": false
+    },
+    {
+      "line": 29,
+      "pos": [
+        -3.05,
+        -1.13,
+        -3.85,
+        -1.12
+      ],
+      "top": null,
+      "bottom": null,
+      "within": 2,
+      "show": true,
+      "ctl": [
+        -3.0541030229717814,
+        -1.9300894794974452
+      ],
+      "start": false
+    }
+  ],
+  "column": [],
+  "surplus": [],
+  "slideDoor": [],
+  "groundCase": [],
+  "tagging": [
+    {
+      "pos": [
+        -3.7155084740509583,
+        -2.8366534571984285
+      ],
+      "title": "厨房",
+      "content": "",
+      "showTitle": true,
+      "showContent": true
+    },
+    {
+      "pos": [
+        -2.3143105862424243,
+        1.1612895425871692
+      ],
+      "title": "客厅",
+      "content": "",
+      "showTitle": true,
+      "showContent": true
+    },
+    {
+      "pos": [
+        1.426755492759659,
+        2.255814150539641
+      ],
+      "title": "次卧",
+      "content": "",
+      "showTitle": true,
+      "showContent": true
+    },
+    {
+      "pos": [
+        4.3935227556422145,
+        2.108601023292294
+      ],
+      "title": "次卧",
+      "content": "",
+      "showTitle": true,
+      "showContent": true
+    },
+    {
+      "pos": [
+        3.8945647069398435,
+        -1.6596322853912704
+      ],
+      "title": "主卧",
+      "content": "",
+      "showTitle": true,
+      "showContent": true
+    },
+    {
+      "pos": [
+        0.8412600089006284,
+        -1.6480069004737916
+      ],
+      "title": "卫生间",
+      "content": "",
+      "showTitle": true,
+      "showContent": true
+    },
+    {
+      "pos": [
+        -1.4465076887007704,
+        -1.9381092549703172
+      ],
+      "title": "餐厅",
+      "content": "",
+      "showTitle": true,
+      "showContent": true
+    }
+  ],
+  "bayCase": [],
+  "furnColumn": [],
+  "furnFlue": [],
+  "dire": 0,
+  "img": {
+    "file": "images/imagest-ZJxR2s0/floorplan.png",
+    "width": 2880,
+    "height": 1620,
+    "left": 676.4508254716981,
+    "top": 275.556303035248,
+    "bottom": 278.0280719647519,
+    "right": 727.830424528302,
+    "bound": {
+      "left": -4.76,
+      "top": -3.63,
+      "bottom": 4.03,
+      "right": 5.84
+    },
+    "upload": true
+  }
+}

BIN
static/images/cad_column.png


BIN
static/images/cad_door.png


BIN
static/images/cad_window.png


BIN
static/images/icon_biaochi_defult.png


BIN
static/images/icon_biaochi_select.png


BIN
static/images/icon_feiji.png


BIN
static/images/shot.png


+ 133 - 0
static/index.html

@@ -0,0 +1,133 @@
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+  <meta charset="UTF-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <meta http-equiv="X-UA-Compatible" content="ie=edge">
+  <link rel="stylesheet" href="./static/style/public.css">
+  <title>4DCAD</title>
+</head>
+
+<body>
+  <div class="body">
+    <div id="cad"></div>
+    <!-- <div class="content">
+      <ul>
+        <li>
+          <div class="itemTitle">
+            <span>增添细节</span>
+          </div>
+          <ul class="chose" id="architecture">
+            <li attr-type="user-line">
+              <div><i></i></div><span>随画</span>
+            </li>
+            <li attr-type="door">
+              <div><i class="iconfont icon_door"></i></div><span>门</span>
+            </li>
+            <li attr-type="casement">
+              <div><i class="iconfont icon_window"></i></div><span>窗户</span>
+            </li>
+            <li attr-type="column">
+              <div><i class="iconfont icon_column"></i></div><span>柱子</span>
+            </li>
+            <li attr-type="point">
+              <div><i class="iconfont icon_point"></i></div><span>点</span>
+            </li>
+            <li attr-type="slideDoor">
+              <div><i class="iconfont icon_point"></i></div><span>移门</span>
+            </li>
+            <li attr-type="tagging">
+              <div><i class="iconfont icon_point"></i></div><span>标注</span>
+            </li>
+            <li attr-type="groundCase">
+              <div><i class="iconfont icon_point"></i></div><span>落地窗</span>
+            </li>
+            <li attr-type="bayCase">
+              <div><i class="iconfont icon_point"></i></div><span>飘窗</span>
+            </li>
+            <li attr-type="furnColumn">
+              <div><i class="iconfont icon_column"></i></div><span>独立柱</span>
+            </li>
+            <li attr-type="furnFlue">
+              <div><i class="iconfont icon_column"></i></div><span>独立柱</span>
+            </li>
+            <li attr-type="line">
+              <div><i class="iconfont icon_column"></i></div><span>墙</span>
+            </li>
+          </ul>
+
+          <a id="aaa">导出</a>
+          <a id="bbb">测试</a>
+        </li>
+      </ul>
+
+      <a class="btn" id="resove">&lt;</a>
+      <a class="btn" id="back">&gt;</a>
+    </div> -->
+  </div>
+
+</body>
+
+</html>
+<script src="./static/three95.min.js"></script>
+<script src="./static/jquery-2.1.1.min.js"></script>
+<script src="./static/data.js"></script>
+<script>
+  
+  //解析查询字符串
+  function getQueryStringArgs() {
+    //取得查询字符串,并去掉开头'?'
+    var qs = location.search.length ? location.search.substring(1) : '';
+    //保存数据的对象
+    var args = {},
+      //以分割符'&'分割字符串,并以数组形式返回
+      items = qs.length ? qs.split('&') : [],
+      item = null,
+      name = null,
+      value = null,
+      i = 0,
+      len = items.length;
+    //逐个将每一项添加到args对象中
+    for (; i < len; i++) {
+      item = items[i].split('=');
+      //解码操作,因为查询字符串经过编码的
+      name = decodeURIComponent(item[0]);
+      value = decodeURIComponent(item[1]);
+      value = item[1];
+      if (name.length) {
+        args[name] = value;
+      }
+    }
+    return args;
+  }
+
+
+  function main() {
+    let args = getQueryStringArgs()
+
+    $.ajax({
+      url: 'https://4dkk.4dage.com/data/data' + args.m + '/floor.json?m=36',
+      method: 'GET',
+      success(data) {
+        let $layer = document.querySelector('#cad')
+        window.cad = structureCAD({
+          data:
+          {
+            block: [],
+            column: [],
+            door: [],
+            hole: [],
+            segment: [],
+            "vertex-xy": [],
+            "vertex-z": []
+          }, layer: $layer
+        });
+
+        cad.loadData(JSON.parse(data));
+      }
+    })
+  }
+
+  main()
+</script>

File diff suppressed because it is too large
+ 4 - 0
static/jquery-2.1.1.min.js


+ 0 - 0
static/style/public.css


Some files were not shown because too many files changed in this diff