From f58b1d5b622f4a6cf06b0e390aef2dd7e413b75a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=AD=90=E6=B6=B5?= <2067519648@qq.com> Date: Thu, 18 Jul 2024 06:41:07 +0000 Subject: [PATCH] =?UTF-8?q?!2856=20=E5=8A=9F=E8=83=BD:=20=E5=9C=86?= =?UTF-8?q?=E5=BC=A7=E6=9D=BF=E7=9A=84=E6=9D=BF=E4=BB=B6=E7=9B=B8=E5=88=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- __test__/Board/ArcBoardSubtract.test.ts | 231 +++++++++ __test__/Booloperate/ArcBoardCutting.test.ts | 2 +- .../BoardCutting/BoardCuttingForSweep.ts | 444 ++++++++++++++++++ src/Add-on/BoardCutting/LinearCutting.ts | 5 +- .../BoardCutting/LinearCuttingForSweep.ts | 19 +- src/DatabaseServices/Entity/Board.ts | 9 + src/DatabaseServices/Entity/Extrude.ts | 14 +- 7 files changed, 711 insertions(+), 13 deletions(-) create mode 100644 __test__/Board/ArcBoardSubtract.test.ts create mode 100644 src/Add-on/BoardCutting/BoardCuttingForSweep.ts diff --git a/__test__/Board/ArcBoardSubtract.test.ts b/__test__/Board/ArcBoardSubtract.test.ts new file mode 100644 index 000000000..fd335aab7 --- /dev/null +++ b/__test__/Board/ArcBoardSubtract.test.ts @@ -0,0 +1,231 @@ +import { Board } from "../../src/DatabaseServices/Entity/Board"; +import { LoadEntityFromFileData } from "../Utils/LoadEntity.util"; + +// 测试命令(用于复制):npm run test -- ArcBoardSubtract.test.ts + +test("刀:普通板 肉:圆弧板 结果:切断", () => +{ + const d1 = { "file": [1, "Board", 10, 2, 100, 0, 1, 2, 71, [0, 1, 0, 0, -1, 0, 0, 0, 0, 0, 1, 0, 2844.615760812303, 714.0686896238476, 0, 1], 0, 0, 1, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 2844.615760812303, 714.0686896238476, 0, 1], 0, 0, 1, 3, 600, 1200, 18, true, "Polyline", 10, 2, 0, 0, 0, 7, 71, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], 0, 0, 1, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], 0, 0, 1, 2, 4, [0, 0], 0, [1200, 0], 0, [1200, 600], 0, [0, 600], 0, true, 0, 3, 0, 0, 0, 0, 0, 20, 0, "层板", "", "", "", "", "", 0, 0, "三合一", 2, 0, "1", "1", "1", "1", "", "", "", 4, "三合一", "三合一", "三合一", "三合一", true, true, 0, 0, 0, 0, 0, 0, 0, 0, true, 0, 0, null, 0, 0, "", "", "", "", 0, false, 0, "", 0], "basePt": { "x": 2244.615760812303, "y": 714.0686896238476, "z": 0 }, "ucs": [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 2376.0621081759596, -839.9555225862432, 600, 1] }; + const d2 = { "file": [1, "Board", 10, 2, 970, 0, 1, 11, 71, [0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 2844.615760812303, 1314.068689623848, -582, 1], 969, 0, 1, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 2844.615760812303, 1314.068689623848, -582, 1], 0, 0, 1, 3, 1200, 731.1016555542986, 18, true, "Polyline", 10, 2, 0, 0, 0, 7, 71, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], 0, 0, 1, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], 0, 0, 1, 2, 4, [0, 0], 0, [731.1016555542986, 0], 0, [731.1016555542986, 1200], 0, [0, 1200], 0, true, 0, 3, 0, 0, 0, 0, 0, 20, 1, "左侧板", "主卧", "下柜", "", "", "", 0, 0, "三合一", 2, 0, "1", "1", "1", "1", "", "", "", 4, "三合一", "三合一", "三合一", "三合一", true, true, 0, 0, 0, 0, 0, 0, 0, 0, true, 0, 0, null, 0, 0, "", "", "", "", 0, false, 0, "Polyline", 10, 2, 0, 0, 1, 7, 71, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], 0, 0, 1, [0, 1, 0, 0, 1, 0, 0, 0, 0, 0, -1, 0, -2108.7625671748674, -2844.615760812303, 0, 1], 0, 0, 1, 2, 2, [0, 0], -0.5052943970699916, [0, -600], 0, false, 0, 0, true, 2, -1, 0, 6, 6, 2, 0, 0, 0, 3, 0, 0, 731.10166, 6, 6, 2, 0, 0, 0, 3, 0, 0], "basePt": { "x": 2230.124977120897, "y": 1314.0686896238476, "z": -582 }, "ucs": [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 2376.0621081759596, -839.9555225862432, 600, 1] }; + /** 普通板(高:600, 宽:1200, 厚:18) */ + const br1 = LoadEntityFromFileData(d1)[0] as Board; + /** 圆弧板(高:1200, 宽:731.1, 厚:18) */ + const br2 = LoadEntityFromFileData(d2)[0] as Board; + // 普通切圆弧,在中点处切割 + br2.Subtract([br1]); + expect(br2.Height).toBe(582); + expect(br2.Grooves.length).toBe(0); +}); + +test("刀:普通板 肉:圆弧板 结果:局部挖穿", () => +{ + const d1 = { "file": [1, "Board", 10, 2, 1010, 0, 1, 2, 71, [0, 1, 0, 0, -1, 0, 0, 0, 0, 0, 1, 0, 2844.6157667616917, 2500.24935630908, 0, 1], 0, 0, 1, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 2844.6157667616917, 2500.24935630908, 0, 1], 0, 0, 1, 3, 600, 1200, 18, true, "Polyline", 10, 2, 0, 0, 0, 7, 71, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], 0, 0, 1, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], 0, 0, 1, 2, 4, [0, 0], 0, [1200, 0], 0, [1200, 600], 0, [0, 600], 0, true, 0, 3, 0, 0, 0, 0, 0, 20, 0, "层板", "", "", "", "", "", 0, 0, "三合一", 2, 0, "1", "1", "1", "1", "", "", "", 4, "三合一", "三合一", "三合一", "三合一", true, true, 0, 0, 0, 0, 0, 0, 0, 0, true, 0, 0, null, 0, 0, "", "", "", "", 0, false, 0, "", 0], "basePt": { "x": 2244.6157667616917, "y": 2500.24935630908, "z": 0 }, "ucs": [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1] }; + const d2 = { "file": [1, "Board", 10, 2, 1011, 0, 1, 11, 71, [0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 2844.6157667616917, 2375.1560164326684, -582, 1], 0, 0, 1, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 2844.6157667616917, 2375.1560164326684, -582, 1], 0, 0, 1, 3, 1200, 731.1016555542986, 18, true, "Polyline", 10, 2, 0, 0, 0, 7, 71, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], 0, 0, 1, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], 0, 0, 1, 2, 4, [0, 0], 0, [731.1016555542986, 0], 0, [731.1016555542986, 1200], 0, [0, 1200], 0, true, 0, 3, 0, 0, 0, 0, 0, 20, 1, "左侧板", "主卧", "下柜", "", "", "", 0, 0, "三合一", 2, 0, "1", "1", "1", "1", "", "", "", 4, "三合一", "三合一", "三合一", "三合一", true, true, 0, 0, 0, 0, 0, 0, 0, 0, true, 0, 0, null, 0, 0, "", "", "", "", 0, false, 0, "Polyline", 10, 2, 0, 0, 0, 7, 71, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], 0, 0, 1, [0, 1, 0, 0, 1, 0, 0, 0, 0, 0, -1, 0, -2108.7625671748674, -2844.615760812303, 0, 1], 0, 0, 1, 2, 2, [0, 0], -0.5052943970699916, [0, -600], 0, false, 0, 0, true, 2, -1, 0, 6, 6, 2, 0, 0, 0, 3, 0, 0, 731.10166, 6, 6, 2, 0, 0, 0, 3, 0, 0], "basePt": { "x": 2230.1249830702855, "y": 2375.156016432668, "z": -582 }, "ucs": [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1] }; + /** 普通板(高:600, 宽:1200, 厚:18) */ + const br1 = LoadEntityFromFileData(d1)[0] as Board; + /** 圆弧板(高:1200, 宽:731.1, 厚:18) */ + const br2 = LoadEntityFromFileData(d2)[0] as Board; + // 普通切圆弧,在中点处切割 + br2.Subtract([br1]); + expect(br2.Height).toBe(1200); + expect(br2.Grooves.length).toBe(1); + expect(br2.Grooves[0].Width).toBeCloseTo(376, 0); + expect(br2.Grooves[0].Height).toBe(18); + expect(br2.Grooves[0].Thickness).toBe(18); +}); + +test("刀:普通板 肉:圆弧板 切了又切 结果:不变", () => +{ + const d1 = { "file": [1, "Board", 10, 2, 1010, 0, 1, 2, 71, [0, 1, 0, 0, -1, 0, 0, 0, 0, 0, 1, 0, 2844.6157667616917, 2500.24935630908, 0, 1], 0, 0, 1, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 2844.6157667616917, 2500.24935630908, 0, 1], 0, 0, 1, 3, 600, 1200, 18, true, "Polyline", 10, 2, 0, 0, 0, 7, 71, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], 0, 0, 1, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], 0, 0, 1, 2, 4, [0, 0], 0, [1200, 0], 0, [1200, 600], 0, [0, 600], 0, true, 0, 3, 0, 0, 0, 0, 0, 20, 0, "层板", "", "", "", "", "", 0, 0, "三合一", 2, 0, "1", "1", "1", "1", "", "", "", 4, "三合一", "三合一", "三合一", "三合一", true, true, 0, 0, 0, 0, 0, 0, 0, 0, true, 0, 0, null, 0, 0, "", "", "", "", 0, false, 0, "", 0], "basePt": { "x": 2244.6157667616917, "y": 2500.24935630908, "z": 0 }, "ucs": [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1] }; + const d2 = { "file": [1, "Board", 10, 2, 1011, 0, 1, 11, 71, [0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 2844.6157667616917, 2375.1560164326684, -582, 1], 0, 0, 1, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 2844.6157667616917, 2375.1560164326684, -582, 1], 0, 0, 1, 3, 1200, 731.1016555542986, 18, true, "Polyline", 10, 2, 0, 0, 0, 7, 71, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], 0, 0, 1, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], 0, 0, 1, 2, 4, [0, 0], 0, [731.1016555542986, 0], 0, [731.1016555542986, 1200], 0, [0, 1200], 0, true, 0, 3, 0, 0, 0, 0, 0, 20, 1, "左侧板", "主卧", "下柜", "", "", "", 0, 0, "三合一", 2, 0, "1", "1", "1", "1", "", "", "", 4, "三合一", "三合一", "三合一", "三合一", true, true, 0, 0, 0, 0, 0, 0, 0, 0, true, 0, 0, null, 0, 0, "", "", "", "", 0, false, 0, "Polyline", 10, 2, 0, 0, 0, 7, 71, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], 0, 0, 1, [0, 1, 0, 0, 1, 0, 0, 0, 0, 0, -1, 0, -2108.7625671748674, -2844.615760812303, 0, 1], 0, 0, 1, 2, 2, [0, 0], -0.5052943970699916, [0, -600], 0, false, 0, 0, true, 2, -1, 0, 6, 6, 2, 0, 0, 0, 3, 0, 0, 731.10166, 6, 6, 2, 0, 0, 0, 3, 0, 0], "basePt": { "x": 2230.1249830702855, "y": 2375.156016432668, "z": -582 }, "ucs": [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1] }; + /** 普通板(高:600, 宽:1200, 厚:18) */ + const br1 = LoadEntityFromFileData(d1)[0] as Board; + /** 圆弧板(高:1200, 宽:731.1, 厚:18) */ + const br2 = LoadEntityFromFileData(d2)[0] as Board; + // 普通切圆弧,在中点处切割 + br2.Subtract([br1]); + expect(br2.Height).toBe(1200); + expect(br2.Grooves.length).toBe(1); + expect(br2.Grooves[0].Width).toBeCloseTo(376, 0); + expect(br2.Grooves[0].Height).toBe(18); + expect(br2.Grooves[0].Thickness).toBe(18); + br2.Subtract([br1]); + expect(br2.Height).toBe(1200); + expect(br2.Grooves.length).toBe(1); + expect(br2.Grooves[0].Width).toBeCloseTo(376, 0); + expect(br2.Grooves[0].Height).toBe(18); + expect(br2.Grooves[0].Thickness).toBe(18); +}); + +test("刀:圆弧板 肉:普通板 结果:切断", () => +{ + const d1 = { "file": [1, "Board", 10, 2, 100, 0, 1, 2, 71, [0, 1, 0, 0, -1, 0, 0, 0, 0, 0, 1, 0, 2844.615760812303, 714.0686896238476, 0, 1], 0, 0, 1, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 2844.615760812303, 714.0686896238476, 0, 1], 0, 0, 1, 3, 600, 1200, 18, true, "Polyline", 10, 2, 0, 0, 0, 7, 71, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], 0, 0, 1, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], 0, 0, 1, 2, 4, [0, 0], 0, [1200, 0], 0, [1200, 600], 0, [0, 600], 0, true, 0, 3, 0, 0, 0, 0, 0, 20, 0, "层板", "", "", "", "", "", 0, 0, "三合一", 2, 0, "1", "1", "1", "1", "", "", "", 4, "三合一", "三合一", "三合一", "三合一", true, true, 0, 0, 0, 0, 0, 0, 0, 0, true, 0, 0, null, 0, 0, "", "", "", "", 0, false, 0, "", 0], "basePt": { "x": 2244.615760812303, "y": 714.0686896238476, "z": 0 }, "ucs": [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 2376.0621081759596, -839.9555225862432, 600, 1] }; + const d2 = { "file": [1, "Board", 10, 2, 970, 0, 1, 11, 71, [0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 2844.615760812303, 1314.068689623848, -582, 1], 969, 0, 1, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 2844.615760812303, 1314.068689623848, -582, 1], 0, 0, 1, 3, 1200, 731.1016555542986, 18, true, "Polyline", 10, 2, 0, 0, 0, 7, 71, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], 0, 0, 1, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], 0, 0, 1, 2, 4, [0, 0], 0, [731.1016555542986, 0], 0, [731.1016555542986, 1200], 0, [0, 1200], 0, true, 0, 3, 0, 0, 0, 0, 0, 20, 1, "左侧板", "主卧", "下柜", "", "", "", 0, 0, "三合一", 2, 0, "1", "1", "1", "1", "", "", "", 4, "三合一", "三合一", "三合一", "三合一", true, true, 0, 0, 0, 0, 0, 0, 0, 0, true, 0, 0, null, 0, 0, "", "", "", "", 0, false, 0, "Polyline", 10, 2, 0, 0, 1, 7, 71, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], 0, 0, 1, [0, 1, 0, 0, 1, 0, 0, 0, 0, 0, -1, 0, -2108.7625671748674, -2844.615760812303, 0, 1], 0, 0, 1, 2, 2, [0, 0], -0.5052943970699916, [0, -600], 0, false, 0, 0, true, 2, -1, 0, 6, 6, 2, 0, 0, 0, 3, 0, 0, 731.10166, 6, 6, 2, 0, 0, 0, 3, 0, 0], "basePt": { "x": 2230.124977120897, "y": 1314.0686896238476, "z": -582 }, "ucs": [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 2376.0621081759596, -839.9555225862432, 600, 1] }; + /** 普通板(高:600, 宽:1200, 厚:18) */ + const br1 = LoadEntityFromFileData(d1)[0] as Board; + /** 圆弧板(高:1200, 宽:731.1, 厚:18) */ + const br2 = LoadEntityFromFileData(d2)[0] as Board; + // 圆弧切普通,在中点处切割 + br1.Subtract([br2]); + expect(br1.Width).toBeCloseTo(751.59, 0.01); + expect(br1.Grooves.length).toBe(0); +}); + +test("刀:圆弧板 肉:普通板 结果:挖槽", () => +{ + const d1 = { "file": [1, "Board", 10, 2, 1008, 0, 1, 2, 71, [0, 1, 0, 0, -1, 0, 0, 0, 0, 0, 1, 0, 3918.3533328726858, 854.8219012550192, 0, 1], 0, 0, 1, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 3918.3533328726858, 854.8219012550192, 0, 1], 0, 0, 1, 3, 600, 1200, 18, true, "Polyline", 10, 2, 0, 0, 0, 7, 0, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], 0, 0, 1, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], 0, 0, 1, 2, 4, [0, 0], 0, [1200, 0], 0, [1200, 600], 0, [0, 600], 0, true, 1, 3, 600.0000000000002, 169.58831912099754, 9.680464072488007, false, "Polyline", 10, 2, 0, 0, 0, 7, 0, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -600.0000000000002, 1.7053025658242404e-13, 0, 1], 0, 0, 1, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -600.0000000000002, 1.7053025658242404e-13, 0, 1], 0, 0, 1, 2, 4, [600.0000000000005, 600], -0.5052943970699916, [600.0000000000005, 0], 0, [629.1535240575996, -5.684341886080802e-14], 0.4681159835446612, [629.1535240575993, 600], 0, true, 0, 3, 0, 0, 0, 0, 0, [0, 1, 0, 0, -1, 0, 0, 0, 0, 0, 1, 0, 3918.3533328726858, 1454.8219012550194, 8.319535927511993, 1], 3, 0, 0, 0, 0, 0, 20, 0, "层板", "", "", "", "", "", 0, 0, "三合一", 2, 0, "1", "1", "1", "1", "", "", "", 4, "三合一", "三合一", "三合一", "三合一", true, true, 0, 0, 0, 0, 0, 0, 0, 0, true, 0, 0, null, 0, 0, "", "", "", "", 0, false, 0, "", 0], "basePt": { "x": 3318.3533328726858, "y": 854.8219012550192, "z": 0 }, "ucs": [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 2376.0621081759596, -839.9555225862432, 600, 1] }; + const d2 = { "file": [1, "Board", 10, 2, 1009, 0, 1, 11, 71, [0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 3918.3533328726858, 1454.8219012550196, 8.319535927511993, 1], 0, 0, 1, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 3918.3533328726858, 1454.8219012550196, 8.319535927511993, 1], 0, 0, 1, 3, 1200, 731.1016555542986, 18, true, "Polyline", 10, 2, 0, 0, 0, 7, 71, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], 0, 0, 1, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], 0, 0, 1, 2, 4, [0, 0], 0, [731.1016555542986, 0], 0, [731.1016555542986, 1200], 0, [0, 1200], 0, true, 0, 3, 0, 0, 0, 0, 0, 20, 1, "左侧板", "主卧", "下柜", "", "", "", 0, 0, "三合一", 2, 0, "1", "1", "1", "1", "", "", "", 4, "三合一", "三合一", "三合一", "三合一", true, true, 0, 0, 0, 0, 0, 0, 0, 0, true, 0, 0, null, 0, 0, "", "", "", "", 0, false, 0, "Polyline", 10, 2, 0, 0, 0, 7, 71, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], 0, 0, 1, [0, 1, 0, 0, 1, 0, 0, 0, 0, 0, -1, 0, -2108.7625671748674, -2844.615760812303, 0, 1], 0, 0, 1, 2, 2, [0, 0], -0.5052943970699916, [0, -600], 0, false, 0, 0, true, 2, -1, 0, 6, 6, 2, 0, 0, 0, 3, 0, 0, 731.10166, 6, 6, 2, 0, 0, 0, 3, 0, 0], "basePt": { "x": 3303.8625491812795, "y": 1454.8219012550192, "z": 8.319535927511993 }, "ucs": [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 2376.0621081759596, -839.9555225862432, 600, 1] }; + /** 普通板(高:600, 宽:1200, 厚:18) */ + const br1 = LoadEntityFromFileData(d1)[0] as Board; + /** 圆弧板(高:1200, 宽:731.1, 厚:18) */ + const br2 = LoadEntityFromFileData(d2)[0] as Board; + // 圆弧切普通,在中点处切割 + br1.Subtract([br2]); + expect(br1.Width).toBe(1200); + expect(br1.Grooves.length).toBe(1); + expect(br1.Grooves[0].Width).toBeCloseTo(169.58, 0.01); + expect(br1.Grooves[0].Height).toBeCloseTo(600, 0.01); + expect(br1.Grooves[0].Thickness).toBeCloseTo(9.68, 0.01); +}); + +test("刀:圆弧板 肉:普通板 结果:局部挖槽", () => +{ + const d1 = { "file": [1, "Board", 10, 2, 1012, 0, 1, 2, 71, [0, 1, 0, 0, -1, 0, 0, 0, 0, 0, 1, 0, 3932.844116564092, -724.8340749354102, 0, 1], 0, 0, 1, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 3932.844116564092, -724.8340749354102, 0, 1], 0, 0, 1, 3, 600, 1200, 18, true, "Polyline", 10, 2, 0, 0, 0, 7, 71, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], 0, 0, 1, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], 0, 0, 1, 2, 4, [0, 0], 0, [1200, 0], 0, [1200, 600], 0, [0, 600], 0, true, 0, 3, 0, 0, 0, 0, 0, 20, 0, "层板", "", "", "", "", "", 0, 0, "三合一", 2, 0, "1", "1", "1", "1", "", "", "", 4, "三合一", "三合一", "三合一", "三合一", true, true, 0, 0, 0, 0, 0, 0, 0, 0, true, 0, 0, null, 0, 0, "", "", "", "", 0, false, 0, "", 0], "basePt": { "x": 3332.844116564092, "y": -724.8340749354102, "z": 0 }, "ucs": [0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1] }; + const d2 = { "file": [1, "Board", 10, 2, 1013, 0, 1, 11, 71, [0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 3932.844116564092, -124.83407493540972, 8.319535927511993, 1], 0, 0, 1, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 3932.844116564092, -124.83407493540972, 8.319535927511993, 1], 0, 0, 1, 3, 1200.0000000000002, 731.1016555542988, 18, false, "Polyline", 10, 2, 0, 0, 0, 7, 71, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 731.1016555542988, 1200.0000000000002, 0, 1], 0, 0, 1, [0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, -2416.101580598777, -1719.742665179213, 0, 1], 0, 0, 1, 2, 8, [-731.1016555542988, 0], 0, [-731.1016555542988, -1200.0000000000002], 0, [-554.9480555542982, -1200.0000000000002], 0, [-435.6008555542985, -958.6773333333331], 0, [-275.9336555542982, -958.6773333333331], 0, [-163.03765555429845, -1200.0000000000002], 0, [0, -1200.0000000000002], 0, [0, 0], 0, true, 0, 3, 0, 0, 0, 0, 0, 20, 1, "左侧板", "主卧", "下柜", "", "", "", 0, 0, "三合一", 2, 8, 1, 1, 1, 1, 1, 1, 1, 1, "1", "1", "1", "1", "", "", "", 8, "三合一", "三合一", "三合一", "三合一", "三合一", "三合一", "三合一", "三合一", true, true, 0, 0, 0, 0, 0, 0, 0, 0, true, 0, 0, null, 0, 0, "", "", "", "", 8, { "description": "" }, { "description": "" }, { "description": "" }, { "description": "" }, { "description": "" }, { "description": "" }, { "description": "" }, { "description": "" }, false, 0, "Polyline", 10, 2, 0, 0, 0, 7, 71, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], 0, 0, 1, [0, 1, 0, 0, 1, 0, 0, 0, 0, 0, -1, 0, -2108.7625671748674, -2844.615760812303, 0, 1], 0, 0, 1, 2, 2, [0, 0], -0.5052943970699916, [0, -600], 0, false, 0, 0, true, 2, -1, 0, 6, 6, 2, 0, 0, 0, 3, 0, 0, 731.10166, 6, 6, 2, 0, 0, 0, 3, 0, 0], "basePt": { "x": 3318.3533328726858, "y": -124.83409721823728, "z": 8.319535927512192 }, "ucs": [0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1] }; + /** 普通板(高:600, 宽:1200, 厚:18) */ + const br1 = LoadEntityFromFileData(d1)[0] as Board; + /** 圆弧异形版(高:1200, 宽:731.1, 厚:18) */ + const br2 = LoadEntityFromFileData(d2)[0] as Board; + // 圆弧切普通,在中点处切割 + br1.Subtract([br2]); + expect(br1.Width).toBe(1200); + expect(br1.Grooves.length).toBe(2); + expect(br1.Grooves[0].Width).toBeCloseTo(120.48, 0.01); + expect(br1.Grooves[0].Height).toBeCloseTo(119.12, 0.01); + expect(br1.Grooves[0].Thickness).toBeCloseTo(9.68, 0.01); + expect(br1.Grooves[1].Width).toBeCloseTo(126.77, 0.01); + expect(br1.Grooves[1].Height).toBeCloseTo(130.37, 0.01); + expect(br1.Grooves[1].Thickness).toBeCloseTo(9.68, 0.01); +}); + +test("刀:圆弧板 肉:普通板 切了又切 结果:不变", () => +{ + const d1 = { "file": [1, "Board", 10, 2, 1012, 0, 1, 2, 71, [0, 1, 0, 0, -1, 0, 0, 0, 0, 0, 1, 0, 3932.844116564092, -724.8340749354102, 0, 1], 0, 0, 1, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 3932.844116564092, -724.8340749354102, 0, 1], 0, 0, 1, 3, 600, 1200, 18, true, "Polyline", 10, 2, 0, 0, 0, 7, 71, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], 0, 0, 1, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], 0, 0, 1, 2, 4, [0, 0], 0, [1200, 0], 0, [1200, 600], 0, [0, 600], 0, true, 0, 3, 0, 0, 0, 0, 0, 20, 0, "层板", "", "", "", "", "", 0, 0, "三合一", 2, 0, "1", "1", "1", "1", "", "", "", 4, "三合一", "三合一", "三合一", "三合一", true, true, 0, 0, 0, 0, 0, 0, 0, 0, true, 0, 0, null, 0, 0, "", "", "", "", 0, false, 0, "", 0], "basePt": { "x": 3332.844116564092, "y": -724.8340749354102, "z": 0 }, "ucs": [0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1] }; + const d2 = { "file": [1, "Board", 10, 2, 1013, 0, 1, 11, 71, [0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 3932.844116564092, -124.83407493540972, 8.319535927511993, 1], 0, 0, 1, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 3932.844116564092, -124.83407493540972, 8.319535927511993, 1], 0, 0, 1, 3, 1200.0000000000002, 731.1016555542988, 18, false, "Polyline", 10, 2, 0, 0, 0, 7, 71, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 731.1016555542988, 1200.0000000000002, 0, 1], 0, 0, 1, [0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, -2416.101580598777, -1719.742665179213, 0, 1], 0, 0, 1, 2, 8, [-731.1016555542988, 0], 0, [-731.1016555542988, -1200.0000000000002], 0, [-554.9480555542982, -1200.0000000000002], 0, [-435.6008555542985, -958.6773333333331], 0, [-275.9336555542982, -958.6773333333331], 0, [-163.03765555429845, -1200.0000000000002], 0, [0, -1200.0000000000002], 0, [0, 0], 0, true, 0, 3, 0, 0, 0, 0, 0, 20, 1, "左侧板", "主卧", "下柜", "", "", "", 0, 0, "三合一", 2, 8, 1, 1, 1, 1, 1, 1, 1, 1, "1", "1", "1", "1", "", "", "", 8, "三合一", "三合一", "三合一", "三合一", "三合一", "三合一", "三合一", "三合一", true, true, 0, 0, 0, 0, 0, 0, 0, 0, true, 0, 0, null, 0, 0, "", "", "", "", 8, { "description": "" }, { "description": "" }, { "description": "" }, { "description": "" }, { "description": "" }, { "description": "" }, { "description": "" }, { "description": "" }, false, 0, "Polyline", 10, 2, 0, 0, 0, 7, 71, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], 0, 0, 1, [0, 1, 0, 0, 1, 0, 0, 0, 0, 0, -1, 0, -2108.7625671748674, -2844.615760812303, 0, 1], 0, 0, 1, 2, 2, [0, 0], -0.5052943970699916, [0, -600], 0, false, 0, 0, true, 2, -1, 0, 6, 6, 2, 0, 0, 0, 3, 0, 0, 731.10166, 6, 6, 2, 0, 0, 0, 3, 0, 0], "basePt": { "x": 3318.3533328726858, "y": -124.83409721823728, "z": 8.319535927512192 }, "ucs": [0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1] }; + /** 普通板(高:600, 宽:1200, 厚:18) */ + const br1 = LoadEntityFromFileData(d1)[0] as Board; + /** 圆弧异形版(高:1200, 宽:731.1, 厚:18) */ + const br2 = LoadEntityFromFileData(d2)[0] as Board; + // 圆弧切普通,在中点处切割 + br1.Subtract([br2]); + expect(br1.Width).toBe(1200); + expect(br1.Grooves.length).toBe(2); + expect(br1.Grooves[0].Width).toBeCloseTo(120.48, 0.01); + expect(br1.Grooves[0].Height).toBeCloseTo(119.12, 0.01); + expect(br1.Grooves[0].Thickness).toBeCloseTo(9.68, 0.01); + expect(br1.Grooves[1].Width).toBeCloseTo(126.77, 0.01); + expect(br1.Grooves[1].Height).toBeCloseTo(130.37, 0.01); + expect(br1.Grooves[1].Thickness).toBeCloseTo(9.68, 0.01); + br1.Subtract([br2]); + expect(br1.Width).toBe(1200); + expect(br1.Grooves.length).toBe(2); + expect(br1.Grooves[0].Width).toBeCloseTo(120.48, 0.01); + expect(br1.Grooves[0].Height).toBeCloseTo(119.12, 0.01); + expect(br1.Grooves[0].Thickness).toBeCloseTo(9.68, 0.01); + expect(br1.Grooves[1].Width).toBeCloseTo(126.77, 0.01); + expect(br1.Grooves[1].Height).toBeCloseTo(130.37, 0.01); + expect(br1.Grooves[1].Thickness).toBeCloseTo(9.68, 0.01); +}); + +test("刀:圆弧板 肉:普通板 斜切 结果:切断", () => +{ + const d1 = { "file": [1, "Board", 10, 2, 1515, 0, 1, 2, 71, [0, 1, 0, 0, -1, 0, 0, 0, 0, 0, 1, 0, 5079.387313671717, 782.5438387097381, 0, 1], 0, 0, 1, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 5079.387313671717, 782.5438387097381, 0, 1], 0, 0, 1, 3, 600, 1200, 18, true, "Polyline", 10, 2, 0, 0, 0, 7, 71, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], 0, 0, 1, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], 0, 0, 1, 2, 4, [0, 0], 0, [1200, 0], 0, [1200, 600], 0, [0, 600], 0, true, 0, 3, 0, 0, 0, 0, 0, 20, 0, "层板", "", "", "", "", "", 0, 0, "三合一", 2, 0, "1", "1", "1", "1", "", "", "", 4, "三合一", "三合一", "三合一", "三合一", true, true, 0, 0, 0, 0, 0, 0, 0, 0, true, 0, 0, null, 0, 0, "", "", "", "", 0, false, 0, "", 0], "basePt": { "x": 4479.387313671717, "y": 782.5438387097381, "z": 0 }, "ucs": [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1] }; + const d2 = { "file": [1, "Board", 10, 2, 1516, 0, 1, 11, 71, [0, 0.9659258262890683, 0.25881904510252074, 0, 0, -0.25881904510252074, 0.9659258262890683, 0, 1, 0, 0, 0, 5079.387313671717, 1382.5438387097386, -582, 1], 0, 0, 1, [1, 0, 0, 0, 0, 0.9659258262890683, 0.25881904510252074, 0, 0, -0.25881904510252074, 0.9659258262890683, 0, 5079.387313671717, 1382.5438387097386, -582, 1], 0, 0, 1, 3, 1200, 731.1016555542986, 18, true, "Polyline", 10, 2, 0, 0, 0, 7, 0, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], 0, 0, 1, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], 0, 0, 1, 2, 4, [0, 0], 0, [731.1016555542986, 0], 0, [731.1016555542986, 1200], 0, [0, 1200], 0, true, 0, 3, 0, 0, 0, 0, 0, 20, 1, "左侧板", "主卧", "下柜", "", "", "", 0, 0, "三合一", 2, 0, "1", "1", "1", "1", "", "", "", 4, "三合一", "三合一", "三合一", "三合一", true, true, 0, 0, 0, 0, 0, 0, 0, 0, true, 0, 0, null, 0, 0, "", "", "", "", 0, false, 0, "Polyline", 10, 2, 0, 0, 0, 7, 0, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], 0, 0, 1, [0, 1, 0, 0, 1, 0, 0, 0, 0, 0, -1, 0, -2108.7625671748674, -2844.615760812303, 0, 1], 0, 0, 1, 2, 2, [0, 0], -0.5052943970699916, [0, -600], 0, false, 0, 0, true, 2, -1, 0, 6, 6, 2, 0, 0, 0, 3, 0, 0, 731.10166, 6, 6, 2, 0, 0, 0, 3, 0, 0], "basePt": { "x": 4464.89652998031, "y": 1071.9609845867133, "z": -582.0000000000001 }, "ucs": [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1] }; + /** 普通板(高:600, 宽:1200, 厚:18) */ + const br1 = LoadEntityFromFileData(d1)[0] as Board; + /** 圆弧板(高:1200, 宽:731.1, 厚:18) */ + const br2 = LoadEntityFromFileData(d2)[0] as Board; + // 圆弧切普通,二者比垂直多15° + br1.Subtract([br2]); + expect(br1.Width).toBeCloseTo(596, 0); + expect(br1.Grooves.length).toBe(0); +}); + +test("刀:圆弧板 肉:普通板 斜切 结果:切断", () => +{ + const d1 = { "file": [1, "Board", 10, 2, 102, 0, 1, 2, 71, [0, 1, 0, 0, -1, 0, 0, 0, 0, 0, 1, 0, 1723.1818533625922, -1442.355647612756, 582, 1], 0, 0, 1, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1723.1818533625922, -1442.355647612756, 582, 1], 0, 0, 1, 3, 600, 1200, 18, true, "Polyline", 10, 2, 0, 0, 0, 7, 71, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], 0, 0, 1, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], 0, 0, 1, 2, 4, [0, 0], 0, [1200, 0], 0, [1200, 600], 0, [0, 600], 0, true, 0, 3, 0, 0, 0, 0, 0, 20, 0, "层板", "", "", "", "", "", 0, 0, "三合一", 2, 0, "1", "1", "1", "1", "", "", "", 4, "三合一", "三合一", "三合一", "三合一", true, true, 0, 0, 0, 0, 0, 0, 0, 0, true, 0, 0, null, 0, 0, "", "", "", "", 0, false, 0, "", 0], "basePt": { "x": 1123.1818533625922, "y": -1442.355647612756, "z": 582 }, "ucs": [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1] }; + const d2 = { "file": [1, "Board", 10, 2, 103, 0, 1, 11, 71, [0, 0.788010753606722, 0.6156614753256583, 0, 0, -0.6156614753256583, 0.788010753606722, 0, 1, 0, 0, 0, 1723.1818533625922, -842.3556476127555, 5.684341886080802e-14, 1], 0, 0, 1, [1, 0, 0, 0, 0, 0.788010753606722, 0.6156614753256583, 0, 0, -0.6156614753256583, 0.788010753606722, 0, 1723.1818533625922, -842.3556476127555, 5.684341886080802e-14, 1], 0, 0, 1, 3, 1200, 731.1016555542986, 18, true, "Polyline", 10, 2, 0, 0, 0, 7, 71, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], 0, 0, 1, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], 0, 0, 1, 2, 4, [0, 0], 0, [731.1016555542986, 0], 0, [731.1016555542986, 1200], 0, [0, 1200], 0, true, 0, 3, 0, 0, 0, 0, 0, 20, 1, "左侧板", "主卧", "下柜", "", "", "", 0, 0, "三合一", 2, 0, "1", "1", "1", "1", "", "", "", 4, "三合一", "三合一", "三合一", "三合一", true, true, 0, 0, 0, 0, 0, 0, 0, 0, true, 0, 0, null, 0, 0, "", "", "", "", 0, false, 0, "Polyline", 10, 2, 0, 0, 0, 7, 71, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], 0, 0, 1, [0, 1, 0, 0, 1, 0, 0, 0, 0, 0, -1, 0, -2108.7625671748674, -2844.615760812303, 0, 1], 0, 0, 1, 2, 2, [0, 0], -0.5052943970699916, [0, -600], 0, false, 0, 0, true, 2, -1, 0, 6, 6, 2, 0, 0, 0, 3, 0, 0, 731.10166, 6, 6, 2, 0, 0, 0, 3, 0, 0], "basePt": { "x": 1108.691069671186, "y": -1581.1494180035459, "z": -1.7063255140878815e-13 }, "ucs": [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1] }; + /** 普通板(高:600, 宽:1200, 厚:18) */ + const br1 = LoadEntityFromFileData(d1)[0] as Board; + /** 圆弧板(高:1200, 宽:731.1, 厚:18) */ + const br2 = LoadEntityFromFileData(d2)[0] as Board; + // 圆弧切普通,二者比垂直多38° + br1.Subtract([br2]); + expect(br1.Width).toBeCloseTo(324, 0); + expect(br1.Grooves.length).toBe(0); +}); + +test("刀:圆弧板 肉:普通板 平切 结果:切断", () => +{ + const d1 = { "file": [1, "Board", 10, 2, 1519, 0, 1, 2, 71, [0, 1, 0, 0, -1, 0, 0, 0, 0, 0, 1, 0, 2844.6157667616917, 3878.148959860686, 0, 1], 0, 0, 1, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 2844.6157667616917, 3878.148959860686, 0, 1], 0, 0, 1, 3, 600, 1200, 18, true, "Polyline", 10, 2, 0, 0, 0, 7, 71, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], 0, 0, 1, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], 0, 0, 1, 2, 4, [0, 0], 0, [1200, 0], 0, [1200, 600], 0, [0, 600], 0, true, 0, 3, 0, 0, 0, 0, 0, 20, 0, "层板", "", "", "", "", "", 0, 0, "三合一", 2, 0, "1", "1", "1", "1", "", "", "", 4, "三合一", "三合一", "三合一", "三合一", true, true, 0, 0, 0, 0, 0, 0, 0, 0, true, 0, 0, null, 0, 0, "", "", "", "", 0, false, 0, "", 0], "basePt": { "x": 2244.6157667616917, "y": 3878.148959860686, "z": 0 }, "ucs": [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1] }; + const d2 = { "file": [1, "Board", 10, 2, 1520, 0, 1, 2, 71, [0, 1, 0, 0, -1, 0, 0, 0, 0, 0, 1, 0, 2844.6157667616917, 4397.2640276069515, -5.684341886080802e-14, 1], 0, 0, 1, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 2844.6157667616917, 4397.2640276069515, -5.684341886080802e-14, 1], 0, 0, 1, 3, 600, 715.3786607514678, 18, true, "Polyline", 10, 2, 0, 0, 0, 7, 71, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], 0, 0, 1, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], 0, 0, 1, 2, 4, [0, 0], 0, [715.3786607514678, 0], 0, [715.3786607514678, 600], 0, [0, 600], 0, true, 0, 3, 0, 0, 0, 0, 0, 20, 0, "层板", "", "", "", "", "", 0, 0, "三合一", 2, 0, "1", "1", "1", "1", "", "", "", 4, "三合一", "三合一", "三合一", "三合一", true, true, 0, 0, 0, 0, 0, 0, 0, 0, true, 0, 0, null, 0, 0, "", "", "", "", 0, false, 0, "Polyline", 10, 2, 0, 0, 1, 7, 71, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], 0, 0, 1, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -2244.6157667616917, -5232.533515779765, 0, 1], 0, 0, 1, 2, 2, [0, 0], -0.4674291876096676, [600, 0], 0, false, 0, 0, true, 2, -1, 0, 6, 6, 2, 0, 0, 0, 3, 0, 0, 715.37866, 6, 6, 2, 0, 0, 0, 3, 0, 0], "basePt": { "x": 2244.6157667616917, "y": 4383.453943263995, "z": -4.263256414560601e-14 }, "ucs": [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1] }; + /** 普通板(高:600, 宽:1200, 厚:18) */ + const br1 = LoadEntityFromFileData(d1)[0] as Board; + /** 圆弧板(高:600, 宽:731.1, 厚:18) */ + const br2 = LoadEntityFromFileData(d2)[0] as Board; + // 圆弧切普通,二者平行 + br1.Subtract([br2]); + expect(br1.Width).toBeCloseTo(505, 0); + expect(br1.Grooves.length).toBe(0); +}); + +test("刀:圆弧异形板 肉:普通板 结果:局部挖槽", () => +{ + const d1 = { "file": [1, "Board", 10, 2, 1529, 0, 1, 2, 71, [0, 1, 0, 0, -1, 0, 0, 0, 0, 0, 1, 0, 5122.2705772876525, 3981.1323362110998, 18, 1], 0, 0, 1, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 5122.2705772876525, 3981.1323362110998, 18, 1], 0, 0, 1, 3, 600, 1200, 18, true, "Polyline", 10, 2, 0, 0, 0, 7, 71, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], 0, 0, 1, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], 0, 0, 1, 2, 4, [0, 0], 0, [1200, 0], 0, [1200, 600], 0, [0, 600], 0, true, 0, 3, 0, 0, 0, 0, 0, 20, 0, "层板", "", "", "", "", "", 0, 0, "三合一", 2, 0, "1", "1", "1", "1", "", "", "", 4, "三合一", "三合一", "三合一", "三合一", true, true, 0, 0, 0, 0, 0, 0, 0, 0, true, 0, 0, null, 0, 0, "", "", "", "", 0, false, 0, "", 0], "basePt": { "x": 4522.2705772876525, "y": 3981.1323362110998, "z": 18 }, "ucs": [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1] }; + const d2 = { "file": [1, "Board", 10, 2, 1530, 0, 1, 11, 71, [0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 5122.2705772876525, 4581.132336211101, 26.319535927511993, 1], 0, 0, 1, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 5122.2705772876525, 4581.132336211101, 26.319535927511993, 1], 0, 0, 1, 3, 1200.4834200687515, 731.1016555542983, 18, false, "Polyline", 10, 2, 0, 0, 0, 7, 0, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 731.1016555542983, 1200.4834200687515, 0, 1], 0, 0, 1, [0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, -5468.647583582021, -111.55040157304632, 0, 1], 0, 0, 1, 2, 8, [-731.1016555542983, -1009.5238095238094], 0.43521583508822825, [-526.5628800440936, -1200], 0, [-365.5508277771496, -1200], 0.41421356237309503, [-185.48542533917862, -1019.9345975620581], -1.000000010997757, [-79.2812473910326, -1019.9345975620581], 0.9999999999999999, [0, -1019.9345975620581], 0, [0, 0], 0, [-731.1016555542983, 0], 0, true, 0, 3, 0, 0, 0, 0, 0, 20, 1, "左侧板", "主卧", "下柜", "", "", "", 0, 0, "三合一", 2, 3, 1, 1, 1, "1", "1", "1", "1", "", "", "", 8, "三合一", "三合一", "三合一", "三合一", "三合一", "三合一", "三合一", "三合一", true, true, 0, 0, 0, 0, 0, 0, 0, 0, true, 0, 0, null, 0, 0, "", "", "", "", 3, { "description": "" }, { "description": "" }, { "description": "" }, false, 0, "Polyline", 10, 2, 0, 0, 0, 7, 0, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], 0, 0, 1, [0, 1, 0, 0, 1, 0, 0, 0, 0, 0, -1, 0, -2108.7625671748674, -2844.615760812303, 0, 1], 0, 0, 1, 2, 2, [0, 0], -0.5052943970699916, [0, -600], 0, false, 0, 0, true, 2, -1, 0, 6, 6, 2, 0, 0, 0, 3, 0, 0, 731.10166, 6, 6, 2, 0, 0, 0, 3, 0, 0], "basePt": { "x": 4507.779793596246, "y": 4581.132313928273, "z": 26.522560493685546 }, "ucs": [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1] }; + /** 普通板(高:600, 宽:1200, 厚:18) */ + const br1 = LoadEntityFromFileData(d1)[0] as Board; + /** 圆弧板(高:1200.48, 宽:731.1, 厚:18) */ + const br2 = LoadEntityFromFileData(d2)[0] as Board; + // 圆弧切普通,在中点处切割 + br1.Subtract([br2]); + expect(br1.Width).toBe(1200); + expect(br1.Grooves.length).toBe(1); + expect(br1.Grooves[0].Width).toBeCloseTo(82.74, 0.01); + expect(br1.Grooves[0].Height).toBeCloseTo(275.55, 0.01); + expect(br1.Grooves[0].Thickness).toBeCloseTo(9.47, 0.01); +}); + +test("刀:圆弧异形板 肉:异形板 结果:局部挖槽", () => +{ + const d1 = { "file": [1, "Board", 10, 2, 1537, 0, 1, 2, 71, [0, 1, 0, 0, -1, 0, 0, 0, 0, 0, 1, 0, 6204.108299754569, 3981.1323362110998, 36, 1], 0, 0, 1, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 6204.108299754569, 3981.1323362110998, 36, 1], 0, 0, 1, 3, 600, 1200.0001828615464, 18, false, "Polyline", 10, 2, 0, 0, 0, 7, 71, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], 0, 0, 1, [0, -1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, -3985.213968864163, 7389.822585468852, -36, 1], 0, 0, 1, 2, 7, [0, 0], 0, [1200, 0], 0, [1200, 103.40136054421782], 0, [1063.9455782312925, 174.149659863946], 0, [1063.9455782312925, 300], 0.9055783921271827, [1064.3809523809532, 600], 0, [0, 600], 0, true, 0, 3, 0, 0, 0, 0, 0, 20, 0, "层板", "", "", "", "", "", 0, 0, "三合一", 2, 7, 1, 1, 1, 1, 1, 1, 1, "1", "1", "1", "1", "", "", "", 7, "三合一", "三合一", "三合一", "三合一", "三合一", "三合一", "三合一", true, true, 0, 0, 0, 0, 0, 0, 0, 0, true, 0, 0, null, 0, 0, "", "", "", "", 7, { "description": "" }, { "description": "" }, { "description": "" }, { "description": "" }, { "description": "" }, { "description": "" }, { "description": "" }, false, 0, "", 0], "basePt": { "x": 5604.108299754569, "y": 3981.1323362110998, "z": 36 }, "ucs": [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 6204.108299754569, 3981.1323362110998, 36, 1] }; + const d2 = { "file": [1, "Board", 10, 2, 1538, 0, 1, 11, 71, [0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 6204.108299754569, 4935.577903727245, 44.31953592751199, 1], 0, 0, 1, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 6204.108299754569, 4935.577903727245, 44.31953592751199, 1], 0, 0, 1, 3, 1200.4834200687515, 731.1016555542983, 18, false, "Polyline", 10, 2, 0, 0, 0, 7, 0, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 731.1016555542983, 1200.4834200687515, 0, 1], 0, 0, 1, [0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, -5468.647583582021, -111.55040157304632, 0, 1], 0, 0, 1, 2, 8, [-731.1016555542983, -1009.5238095238094], 0.43521583508822825, [-526.5628800440936, -1200], 0, [-365.5508277771496, -1200], 0.41421356237309503, [-185.48542533917862, -1019.9345975620581], -1.000000010997757, [-79.2812473910326, -1019.9345975620581], 0.9999999999999999, [0, -1019.9345975620581], 0, [0, 0], 0, [-731.1016555542983, 0], 0, true, 0, 3, 0, 0, 0, 0, 0, 20, 1, "左侧板", "主卧", "下柜", "", "", "", 0, 0, "三合一", 2, 3, 1, 1, 1, "1", "1", "1", "1", "", "", "", 8, "三合一", "三合一", "三合一", "三合一", "三合一", "三合一", "三合一", "三合一", true, true, 0, 0, 0, 0, 0, 0, 0, 0, true, 0, 0, null, 0, 0, "", "", "", "", 3, { "description": "" }, { "description": "" }, { "description": "" }, false, 0, "Polyline", 10, 2, 0, 0, 0, 7, 0, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], 0, 0, 1, [0, 1, 0, 0, 1, 0, 0, 0, 0, 0, -1, 0, -2108.7625671748674, -2844.615760812303, 0, 1], 0, 0, 1, 2, 2, [0, 0], -0.5052943970699916, [0, -600], 0, false, 0, 0, true, 2, -1, 0, 6, 6, 2, 0, 0, 0, 3, 0, 0, 731.10166, 6, 6, 2, 0, 0, 0, 3, 0, 0], "basePt": { "x": 5589.617516063163, "y": 4935.5778814444175, "z": 44.522560493685546 }, "ucs": [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 6204.108299754569, 3981.1323362110998, 36, 1] }; + /** 普通板(高:600, 宽:1200, 厚:18) */ + const br1 = LoadEntityFromFileData(d1)[0] as Board; + /** 圆弧板(高:1200.48, 宽:731.1, 厚:18) */ + const br2 = LoadEntityFromFileData(d2)[0] as Board; + // 圆弧切普通,在中点处切割 + br1.Subtract([br2]); + expect(br1.Width).toBeCloseTo(1200); + expect(br1.Grooves.length).toBe(2); + expect(br1.Grooves[0].Width).toBeCloseTo(55.62, 0.01); + expect(br1.Grooves[0].Height).toBeCloseTo(85.39, 0.01); + expect(br1.Grooves[0].Thickness).toBeCloseTo(8.46, 0.01); + expect(br1.Grooves[1].Width).toBeCloseTo(21.32, 0.01); + expect(br1.Grooves[1].Height).toBeCloseTo(44.80, 0.01); + expect(br1.Grooves[1].Thickness).toBeCloseTo(8.46, 0.01); +}); + +test("刀:圆弧板 肉:圆弧板 结果:切断", () => +{ + const d1 = { "file": [1, "Board", 11, 2, 1512, 0, 1, 11, 71, [0, 6.123233995736766e-17, 1, 0, 0, -1, 6.123233995736766e-17, 0, 1, 0, 0, 0, 6190.533374666166, 3776.6989303777405, 21.355782395544892, 1], 0, 0, 1, [1, 0, 0, 0, 0, 6.123233995736766e-17, 1, 0, 0, -1, 6.123233995736766e-17, 0, 6190.533374666166, 3776.6989303777405, 21.355782395544892, 1], 0, 0, 1, 0, 3, 1200, 731.1016555542986, 18, true, "Polyline", 11, 2, 0, 0, 0, 7, 71, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], 0, 0, 1, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], 0, 0, 1, 0, 2, 4, [0, 0], 0, [731.1016555542986, 0], 0, [731.1016555542986, 1200], 0, [0, 1200], 0, true, 0, 3, 0, 0, 0, 0, 0, 22, 1, "左侧板", "主卧", "下柜", "", "", "", 0, 0, "三合一", 2, 0, "1", "1", "1", "1", "", "", "", 4, "三合一", "三合一", "三合一", "三合一", true, true, 0, 0, 0, 0, 0, 0, 0, 0, true, 0, 0, null, 0, 0, "", "", "", "", 0, false, 0, "Polyline", 11, 2, 0, 0, 0, 7, 71, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], 0, 0, 1, [0, 1, 0, 0, 1, 0, 0, 0, 0, 0, -1, 0, -2108.7625671748674, -2844.615760812303, 0, 1], 0, 0, 1, 0, 2, 2, [0, 0], -0.5052943970699916, [0, -600], 0, false, 0, 0, true, 2, -1, 0, 6, 6, 2, 0, 0, 0, 3, 0, 0, 731.10166, 6, 6, 2, 0, 0, 0, 3, 0, 0, 0], "basePt": { "x": 5576.04259097476, "y": 2576.6989303777405, "z": 21.355782395544523 }, "ucs": [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1] }; + const d2 = { "file": [1, "Board", 11, 2, 1513, 0, 1, 3, 71, [1, 0, 0, 0, 0, 0, 1, 0, 0, -1, 0, 0, 5562.505561677293, 3188.562685125938, -567.9663264066833, 1], 0, 0, 1, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 5562.505561677293, 3188.562685125938, -567.9663264066833, 1], 0, 0, 1, 0, 3, 1200, 742.5233768947737, 18, true, "Polyline", 11, 2, 0, 0, 0, 7, 71, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], 0, 0, 1, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], 0, 0, 1, 0, 2, 4, [0, 0], 0, [742.5233768947737, 0], 0, [742.5233768947737, 1200], 0, [0, 1200], 0, true, 0, 3, 0, 0, 0, 0, 0, 22, 2, "背板", "", "", "", "", "", 0, 0, "三合一", 2, 0, "1", "1", "1", "1", "", "", "", 4, "三合一", "三合一", "三合一", "三合一", true, true, 0, 0, 0, 0, 0, 0, 0, 0, true, 0, 0, null, 0, 0, "", "", "", "", 0, false, 0, "Polyline", 11, 2, 0, 0, 0, 7, 71, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], 0, 0, 1, [1, 0, 0, 0, 0, -1, 0, 0, 0, 0, -1, 0, -5562.505561677293, 3188.562685125938, 0, 1], 0, 0, 1, 0, 2, 2, [0, 0], 0.4532931428414304, [656.0556259777459, 4.547473508864641e-13], 0, false, 0, 1, true, 2, -1, 0, 6, 6, 2, 0, 0, 0, 3, 0, 0, 742.52338, 6, 6, 2, 0, 0, 0, 3, 0, 0, 0], "basePt": { "x": 5562.505561677293, "y": 3176.698930853477, "z": -567.9663264066833 }, "ucs": [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1] }; + /** 肉-圆弧板(高:1200.48, 宽:731.1, 厚:18) */ + const br1 = LoadEntityFromFileData(d1)[0] as Board; + /** 刀-圆弧板(高:1200.48, 宽:742.52, 厚:18) */ + const br2 = LoadEntityFromFileData(d2)[0] as Board; + // 圆弧切圆弧,在中点处切割 + br1.Subtract([br2]); + expect(br1.Height).toBeCloseTo(559.01); + expect(br1.Grooves.length).toBe(0); +}); diff --git a/__test__/Booloperate/ArcBoardCutting.test.ts b/__test__/Booloperate/ArcBoardCutting.test.ts index 854d2270d..c3c2fbbbd 100644 --- a/__test__/Booloperate/ArcBoardCutting.test.ts +++ b/__test__/Booloperate/ArcBoardCutting.test.ts @@ -54,7 +54,7 @@ function TestAll() }, answer: { count: 2, - areas: [445744.72443803976, 394255.27556196053], + areas: [440498.50473467575, 399501.49526532437], lengths: [1343.4513322353837, 1343.4513322353837] } }, diff --git a/src/Add-on/BoardCutting/BoardCuttingForSweep.ts b/src/Add-on/BoardCutting/BoardCuttingForSweep.ts new file mode 100644 index 000000000..13b6797f1 --- /dev/null +++ b/src/Add-on/BoardCutting/BoardCuttingForSweep.ts @@ -0,0 +1,444 @@ +import geom3, { toPolygons } from "@jscad/modeling/src/geometries/geom3"; +import { Geom3 } from "@jscad/modeling/src/geometries/types"; +import { Mat4 } from "@jscad/modeling/src/maths/mat4"; +import { measureAggregateVolume } from "@jscad/modeling/src/measurements"; +import { intersect, scission } from "@jscad/modeling/src/operations/booleans"; +import { Vector2, Vector3 } from "three"; +import { CSGIntersect, Geom3Res } from "../../Common/CSGIntersect"; +import { Board } from "../../DatabaseServices/Entity/Board"; +import { ExtrudeSolid } from "../../DatabaseServices/Entity/Extrude"; +import { Line } from "../../DatabaseServices/Entity/Line"; +import { Polyline } from "../../DatabaseServices/Entity/Polyline"; +import { BSPGroupParse } from "../../Geometry/BSPGroupParse"; +import { Box3Ext } from "../../Geometry/Box"; +import { CreateContour2 } from "../../Geometry/CreateContour2"; +import { MoveMatrix, equaln, equalv3 } from "../../Geometry/GeUtils"; +/** + * @Finish 目前已实现 + * 1. 圆弧板垂直切普通板 + * 2. 普通板穿透切圆弧板 + * @TODO 当前问题记录 + * 1. 普通板切圆弧板,凹槽的长度与厚度如何确定?(目前写死厚度切穿, 长度切断) + * 2. 圆弧板切普通版, 若二者不垂直,存在夹角,怎么办?(目前不考虑) + * 3. 圆弧板切圆弧板,如何处理?(目前不考虑) + * 4. 平行情况,如何处理?(目前不考虑) + * @Log 2024/06/20 + * 处理问题1:采用CSG的包围盒与路径求交方式,来确定凹槽的长度 + * @Log 2024/06/21 + * 发现问题并处理: 若圆弧板为异形板,垂直切普通板,生成的凹槽还是整段路径 + * 发现问题并处理: 由于精度问题,导致切割后在切割,依旧有交集,故这里加了体积过滤 + * @Log 2024/06/25 + * 优化: 优化体积过滤,改用CSG真实的体积计算 + * 重构: 重构了代码结构,修复了普通板切圆弧板的bug(采用点集投影,而非之前的包围盒) + * @Log 2024/06/26 + * 处理问题2:采用对CSG上下面进行投影,然后转为面域做并集,从而生成凹槽 + * @Log 2024/06/27 + * 优化问题2:面域的布尔运算很慢,容易造成肉眼可见的卡顿(约1-2秒),现在改用project + * @Log 2024/07/02 + * 发现问题并处理: project存在精度问题,导致凹槽的长度不准确,故做了修正(若快切断则为切断) + * @Log 2024/07/10 + * 优化: 当普通版斜切圆弧板时,采用逆映射生成凹槽轮廓,而非原有的矩形轮廓 + * @Log 2024/07/11 + * 优化: 改用C2R生成凹槽的轮廓线,而非之前的project(因为它不准) + * 处理问题3:通过逆映射实现圆弧板切圆弧板 + */ + +/** 针对圆弧板的板件切割*/ +export class BoardCuttingForSweep +{ + /** 生成圆弧板的切割器*/ + constructor(private meat: Board, private knife: Board) + { + this.meat = meat; + this.knife = knife; + } + + /** 生成凹槽 */ + ConverToLocalGroove() + { + const meat = this.meat; + const knife = this.knife; + // 确保圆弧板构建成功 + meat.MeshGeometry; + knife.MeshGeometry; + // 二者都是圆弧板 + if (knife.IsArcBoard && meat.IsArcBoard) + return this.ArcCutArc(); + // 刀是圆弧板 + if (knife.IsArcBoard) + return this.ArcCutBoard(); + // 肉是圆弧板 + if (meat.IsArcBoard) + return this.BoardCutArc(); + return []; + } + + /** 圆弧板切普通板 */ + private ArcCutBoard() + { + const meat = this.meat; + const knife = this.knife; + /** 生成轮廓-垂直切 */ + const CreateContour = () => + { + const contours: Polyline[] = []; + // 路径信息 + const path1 = knife.GetSweepPath1InWCS().ApplyMatrix(meat.OCSInv); + const path2 = knife.GetSweepPath2InWCS().ApplyMatrix(meat.OCSInv); + path1.Z0(); + path2.Z0(); + // 不垂直 + if (!equalv3(path1.Normal, new Vector3(0, 0, 1)) && !equalv3(path1.Normal, new Vector3(0, 0, -1))) + return CreateContourByOblique(); + // CSG + const interCSG = CSGIntersect(meat.CSG, knife.CSG, meat.OCSInv.multiply(knife.OCSNoClone)); + const interCSGs = scission(interCSG as unknown as Geom3) as unknown as Geom3[]; + for (const interCSG of interCSGs) + { + // 过滤掉小于肉的1/1000体积的凹槽 + const interVolume = measureAggregateVolume(interCSG); + const meatVolume = measureAggregateVolume(meat.CSG); + if (interVolume < meatVolume / 10000) + continue; + // 投影取点 + const pts = this.ProjectCSGToPts(interCSG, "Z"); + const range1 = this.GetParamRange(pts, path1); + const range2 = this.GetParamRange(pts, path2); + // 获取局部路径 + const sp1 = path1.GetPointAtParam(range1.sParam); + const ep1 = path1.GetPointAtParam(range1.eParam); + const sp2 = path2.GetPointAtParam(range2.sParam); + const ep2 = path2.GetPointAtParam(range2.eParam); + const localPath1 = this.Get_Pl_InAtoB(path1, sp1, ep1); + const localPath2 = this.Get_Pl_InAtoB(path2, sp2, ep2); + // 围成轮廓 + const line1 = new Line(localPath1.StartPoint, localPath2.StartPoint); + const line2 = new Line(localPath1.EndPoint, localPath2.EndPoint); + const contour = localPath1.Clone() as Polyline; + contour.Join(line1); + contour.Join(line2); + contour.Join(localPath2); + contours.push(contour); + } + return contours; + }; + /** 生成轮廓-斜切(任意角度) */ + const CreateContourByOblique = () => + { + const contours: Polyline[] = []; + // CSG + const interCSG = CSGIntersect(meat.CSG, knife.CSG, meat.OCSInv.multiply(knife.OCSNoClone)); + const interCSGs = scission(interCSG as unknown as Geom3) as unknown as Geom3[]; + for (const interCSG of interCSGs) + { + // 过滤掉小于肉的1/1000体积的凹槽 + const interVolume = measureAggregateVolume(interCSG); + const meatVolume = measureAggregateVolume(meat.CSG); + if (interVolume < meatVolume / 10000) + continue; + const contour = this.CSGToPolyline(interCSG); + contours.push(contour); + } + return contours; + }; + /** 生成凹槽 */ + const CreateGroove = (contourCurve: Polyline) => + { + /** 凹槽 */ + const groove = new ExtrudeSolid(); + // @ts-ignore 直接修改即可(省略OCS转换) + groove.contourCurve = contourCurve; + // 获取凹槽的厚度 + const interCSG = CSGIntersect(meat.CSG, knife.CSG, meat.OCSInv.multiply(knife.OCSNoClone)); + const topology = new BSPGroupParse(interCSG); + const interBox = new Box3Ext(); + for (let pts of topology.Parse()) + interBox.setFromPoints(pts); + const interBoxSize = interBox.getSize(new Vector3()); + groove.Thickness = interBoxSize.z; + // 修正位置 + groove.OCS = meat.OCS; + if (interBox.min.z > 0) + groove.ApplyMatrix(MoveMatrix(meat.Normal.multiplyScalar(meat.Thickness - groove.Thickness))); + return groove; + }; + const grooves: ExtrudeSolid[] = []; + // 1.生成轮廓 + const grooveContours = CreateContour(); + // 2.生成凹槽 + for (const grooveContour of grooveContours) + { + const groove = CreateGroove(grooveContour); + grooves.push(groove); + } + return grooves; + } + + /** 普通板切圆弧板 */ + private BoardCutArc() + { + const meat = this.meat; + const knife = this.knife; + /** 生成轮廓-垂直切 */ + const CreateContour = () => + { + const contours: Polyline[] = []; + // 路径信息 + const path = (meat as unknown as Board).GetSweepPathInWCS().ApplyMatrix(meat.OCSInv); + const tempPath = (meat as unknown as Board).GetSweepPathInWCS().ApplyMatrix(knife.OCSInv); + // 不垂直 + if (!equalv3(tempPath.Normal, new Vector3(0, 0, 1)) && !equalv3(tempPath.Normal, new Vector3(0, 0, -1))) + return CreateContourByOblique(); + // CSG + const csg1Clone = geom3.create(meat.CSG.polygons.concat()); + const csg2Clone = geom3.create(knife.CSG.polygons.concat()); + csg1Clone.transforms = meat.OCS.elements as Mat4; + csg2Clone.transforms = knife.OCS.elements as Mat4; + const interCSG = intersect(csg1Clone, csg2Clone); + interCSG.transforms = meat.OCSInv.elements as Mat4; + intersect(interCSG, interCSG); + const interCSGs = scission(interCSG as unknown as Geom3) as unknown as Geom3[]; + for (const interCSG of interCSGs) + { + // 过滤掉小于肉的100体积的凹槽(这里甩固定值) + const interVolume = measureAggregateVolume(interCSG); + if (interVolume < 100) + continue; + const topology = new BSPGroupParse(interCSG as unknown as Geom3Res); + /** 交集部分的包围盒 */ + const box = new Box3Ext(new Vector3(Infinity, Infinity, Infinity), new Vector3(-Infinity, -Infinity, -Infinity)); + const ptsList = topology.Parse(); + for (let pts of ptsList) + { + box.min.x = Math.min(box.min.x, ...pts.map(p => p.x)); + box.min.y = Math.min(box.min.y, ...pts.map(p => p.y)); + box.min.z = Math.min(box.min.z, ...pts.map(p => p.z)); + box.max.x = Math.max(box.max.x, ...pts.map(p => p.x)); + box.max.y = Math.max(box.max.y, ...pts.map(p => p.y)); + box.max.z = Math.max(box.max.z, ...pts.map(p => p.z)); + } + // 投影取点 + const pts = this.ProjectCSGToPts(interCSG, "Y"); + // 计算槽的长度 + const range = this.GetParamRange(pts, path); + // 获取局部路径 + const sDist1 = path.GetDistAtParam(range.sParam); + const eDist1 = path.GetDistAtParam(range.eParam); + /** 凹槽轮廓-多段线 */ + const contour = new Polyline(); + contour.LineData.push({ pt: new Vector2(sDist1, box.min.y), bul: 0 }); + contour.LineData.push({ pt: new Vector2(eDist1, box.min.y), bul: 0 }); + contour.LineData.push({ pt: new Vector2(eDist1, box.max.y), bul: 0 }); + contour.LineData.push({ pt: new Vector2(sDist1, box.max.y), bul: 0 }); + contour.LineData.push({ pt: new Vector2(sDist1, box.min.y), bul: 0 }); + contours.push(contour); + } + return contours; + }; + /** 生成轮廓-斜切(任意角度) */ + const CreateContourByOblique = () => + { + const contours: Polyline[] = []; + // CSG + const CSG = CSGIntersect(meat.CSG, knife.CSG, meat.OCSInv.multiply(knife.OCSNoClone)) as unknown as Geom3; + CSG.transforms = meat.OCS.elements as Mat4; + const interCSGs = scission(CSG as unknown as Geom3) as unknown as Geom3[]; + for (const interCSG of interCSGs) + { + // 过滤掉小于肉的1/1000体积的凹槽 + const interVolume = measureAggregateVolume(interCSG); + const meatVolume = measureAggregateVolume(meat.CSG); + if (interVolume < meatVolume / 10000) + continue; + // 对CSG进行逆映射 + this.InverseCSG(interCSG); + const contour = this.CSGToPolyline(interCSG); + contours.push(contour); + } + return contours; + }; + /** 生成凹槽 */ + const CreateGroove = (contourCurve: Polyline) => + { + /** 凹槽 */ + const groove = new ExtrudeSolid(); + // @ts-ignore 直接修改即可(省略OCS转换) + groove.contourCurve = contourCurve; + // TODO: 获取凹槽的厚度 + groove.Thickness = meat.Thickness; + // 修正位置 + groove.OCS = meat.OCS; + return groove; + }; + const grooves: ExtrudeSolid[] = []; + const grooveContours = CreateContour(); + for (const grooveContour of grooveContours) + { + /** 凹槽 */ + const groove = CreateGroove(grooveContour); + grooves.push(groove); + } + return grooves; + } + + /** 圆弧板切圆弧板 */ + private ArcCutArc() + { + const meat = this.meat; + const knife = this.knife; + /** 生成轮廓 */ + const CreateContour = () => + { + const contours: Polyline[] = []; + // CSG + const CSG = CSGIntersect(meat.CSG, knife.CSG, meat.OCSInv.multiply(knife.OCSNoClone)) as unknown as Geom3; + CSG.transforms = meat.OCS.elements as Mat4; + const interCSGs = scission(CSG as unknown as Geom3) as unknown as Geom3[]; + for (const interCSG of interCSGs) + { + // 过滤掉小于肉的1/1000体积的凹槽 + const interVolume = measureAggregateVolume(interCSG); + const meatVolume = measureAggregateVolume(meat.CSG); + if (interVolume < meatVolume / 10000) + continue; + // 对CSG进行逆映射 + this.InverseCSG(interCSG); + const contour = this.CSGToPolyline(interCSG); + contours.push(contour); + } + return contours; + }; + /** 生成凹槽 */ + const CreateGroove = (contourCurve: Polyline) => + { + /** 凹槽 */ + const groove = new ExtrudeSolid(); + // @ts-ignore 直接修改即可(省略OCS转换) + groove.contourCurve = contourCurve; + // TODO: 获取凹槽的厚度 + groove.Thickness = meat.Thickness; + // 修正位置 + groove.OCS = meat.OCS; + return groove; + }; + const grooves: ExtrudeSolid[] = []; + const grooveContours = CreateContour(); + for (const grooveContour of grooveContours) + { + /** 凹槽 */ + const groove = CreateGroove(grooveContour); + grooves.push(groove); + } + return grooves; + } + + /** 对CSG进行逆映射(根据放样路径) */ + private InverseCSG(interCSG: Geom3) + { + const meat = this.meat; + const pathWCS = meat.GetSweepPathInWCS(); + const path = pathWCS.Clone().ApplyMatrix(pathWCS.OCSInv); + for (const polygon of interCSG.polygons) + { + for (const vertice of polygon.vertices) + { + const p = new Vector3(vertice[0], vertice[1], vertice[2]); + p.applyMatrix4(pathWCS.OCSInv); + const cp = path.GetClosestPointTo(p, false); + const param = Math.max(path.GetParamAtPoint(cp), 0); + const x = path.GetDistAtParam(param); + const y = -p.z; + p.y = y; + p.x = x; + vertice[0] = p.x; + vertice[1] = p.y; + } + } + } + + /** 对CSG进行投影,返回点集 */ + private ProjectCSGToPts(CSG: Geom3, dir: "X" | "Y" | "Z" = "Z") + { + // 转为点集 + const topology = new BSPGroupParse(CSG as unknown as Geom3Res); + const ps: Vector3[] = []; + for (let pts of topology.Parse()) + { + for (const pt of pts) + { + const fuzz = 0.1; + // 对CSG进行投影 + if (dir === "Z") + { + if (!ps.find(p => equaln(p.x, pt.x, fuzz) && equaln(p.y, pt.y, fuzz))) + { + pt.z = 0; + ps.push(pt); + } + } + else if (dir === "Y") + { + if (!ps.find(p => equaln(p.x, pt.x, fuzz) && equaln(p.z, pt.z, fuzz))) + { + pt.y = 0; + ps.push(pt); + } + } + } + } + return ps; + }; + + /** 对CSG进行投影,返回多段线 */ + private CSGToPolyline(CSG: Geom3) + { + const polygons = toPolygons(CSG); + const pls: Polyline[] = []; + for (const polygon of polygons) + { + const pl = new Polyline(); + for (const v of polygon.vertices) + { + const pt = new Vector2(v[0], v[1]); + pl.LineData.push({ pt, bul: 0 }); + pl.CloseMark = true; // 强制闭合 + } + pls.push(pl); + } + return CreateContour2(pls).Curve as Polyline; + }; + + /** 获取CSG在路径上的param范围 */ + private GetParamRange(pts: Vector3[], path: Polyline) + { + // 与路径匹配 + const range = { + sParam: Infinity, + eParam: -Infinity + }; + for (const pt of pts) + { + const cpt = path.GetClosestPointTo(pt, false); + const param = path.GetParamAtPoint(cpt); + if (param || param === 0) + { + range.sParam = Math.min(range.sParam, param); + range.eParam = Math.max(range.eParam, param); + } + } + // 避免应精度出现小于0的情况 + range.sParam = Math.max(0, range.sParam); + return range; + } + + /** 获取pA-pB之间的曲线 */ + private Get_Pl_InAtoB(pl: Polyline, pA: Vector3, pB: Vector3) + { + const paramA = pl.GetParamAtPoint(pA); + const pls = pl.GetSplitCurves(paramA); + pl = pls[1] || pls[0]; + const paramB = pl.GetParamAtPoint(pB); + const pls2 = pl.GetSplitCurves(paramB); + return pls2[0]; + }; +} diff --git a/src/Add-on/BoardCutting/LinearCutting.ts b/src/Add-on/BoardCutting/LinearCutting.ts index c7e9882f6..51f9f2be8 100644 --- a/src/Add-on/BoardCutting/LinearCutting.ts +++ b/src/Add-on/BoardCutting/LinearCutting.ts @@ -201,7 +201,10 @@ export class LinearCutting implements Command const startPoint = sweepPath.StartPoint; sweepPath.Move(new Vector3(-startPoint.x, -startPoint.y, 0)); const rotateMatrix = new Matrix4().extractRotation(br.OCS); - const biasV = new Vector3(startPoint.x, 0, startPoint.y).applyMatrix4(rotateMatrix); + let biasV = new Vector3(startPoint.x, 0, startPoint.y).applyMatrix4(rotateMatrix); + // 针对放样角度的处理 + if (equaln(sweepAngle, Math.PI / 2)) + biasV = new Vector3(0, startPoint.x, startPoint.y).applyMatrix4(rotateMatrix); const biasMatrix = new Matrix4().makeTranslation(biasV.x, biasV.y, biasV.z); br.ApplyMatrix(biasMatrix); // 旋转修正 diff --git a/src/Add-on/BoardCutting/LinearCuttingForSweep.ts b/src/Add-on/BoardCutting/LinearCuttingForSweep.ts index 9b36cc8d5..cc9fb219a 100644 --- a/src/Add-on/BoardCutting/LinearCuttingForSweep.ts +++ b/src/Add-on/BoardCutting/LinearCuttingForSweep.ts @@ -154,24 +154,29 @@ export class LinearCuttingForSweep const basePt = splitData.pt; const backPt = splitDatas[i - 1]?.pt; const nextPt = splitDatas[i + 1]?.pt; + // 目前先只针对首尾点 + if (backPt && nextPt) + continue; /** @todo 这里先按水平延伸来修正 */ - if (yLineMax.PtOnCurve(basePt)) + if (yLineMax.PtOnCurve(basePt, 1e-3)) { let sign = 1; if (backPt) sign = Math.sign(basePt.x - backPt.x); else if (nextPt) sign = Math.sign(basePt.x - nextPt.x); const pt = basePt.clone().add(new Vector3(sign * 2000, 0, 0)); const index = splitData.index + ((backPt && !yLineMax.PtOnCurve(backPt)) ? 0.01 : -0.01); - splitDatas.push({ pt, index }); + splitDatas.unshift({ pt, index }); + i++; } - else if (yLineMin.PtOnCurve(basePt)) + else if (yLineMin.PtOnCurve(basePt, 1e-3)) { let sign = 1; if (backPt) sign = Math.sign(basePt.x - backPt.x); else if (nextPt) sign = Math.sign(basePt.x - nextPt.x); const pt = basePt.clone().add(new Vector3(sign * 2000, 0, 0)); const index = splitData.index + ((backPt && !yLineMin.PtOnCurve(backPt)) ? 0.01 : -0.01); - splitDatas.push({ pt, index }); + splitDatas.unshift({ pt, index }); + i++; } } // 排序 @@ -419,9 +424,7 @@ export class LinearCuttingForSweep const inptMax = zLineMax.IntersectWith(sweepPathInWCS, IntersectOption.ExtendThis, 0.01)[0]; const inptMin = zLineMin.IntersectWith(sweepPathInWCS, IntersectOption.ExtendThis, 0.01)[0]; if (!inptMax || !inptMin) - { return [zLineMax, zLineMin]; - } const paramMax = sweepPathInWCS.GetParamAtPoint(inptMax); const paramMin = sweepPathInWCS.GetParamAtPoint(inptMin); /** 展开后的路径 */ @@ -1036,7 +1039,7 @@ class EntityManager /** 获取实体的包围盒线 */ static GetEnBoxPl(en: Entity) { - const box = en.Clone().ApplyMatrix(en.OCSInv).BoundingBox; + const box = en.BoundingBox; const boxPl = new Polyline([ { pt: new Vector2(box.min.x, box.min.y), @@ -1059,7 +1062,7 @@ class EntityManager bul: 0 }, ]); - boxPl.ApplyMatrix(en.OCS); + boxPl.Z = box.min.z; return boxPl; }; } diff --git a/src/DatabaseServices/Entity/Board.ts b/src/DatabaseServices/Entity/Board.ts index b22c53b2e..2d589a5ad 100644 --- a/src/DatabaseServices/Entity/Board.ts +++ b/src/DatabaseServices/Entity/Board.ts @@ -53,6 +53,7 @@ import { Curve } from './Curve'; import { DragPointType } from './DragPointType'; import { Entity } from './Entity'; import { ExtrudeContourCurve, ExtrudeSolid } from './Extrude'; +import { ExtrudeConfig } from './ExtrudeConfig'; import { GenLocalUv } from './GenLocalUv'; import { Line } from './Line'; import { Polyline } from './Polyline'; @@ -2262,6 +2263,14 @@ export class Board extends ExtrudeSolid if (this._MeshGeometry) return this._MeshGeometry; + if (this.thickness <= 0) + return new BufferGeometry(); + + if (!ExtrudeConfig.DisableRefCut) + this.CalcRelevanceGroove(); + if (this._MeshGeometry) + return this._MeshGeometry; + let build = new ArcBoardBuild(this); [this._MeshGeometry, this._EdgeGeometry] = build.BuildMeshEdgeGeom(); this._SweepArcBoardBuild = build; diff --git a/src/DatabaseServices/Entity/Extrude.ts b/src/DatabaseServices/Entity/Extrude.ts index 20843a9c0..9022e1f4b 100644 --- a/src/DatabaseServices/Entity/Extrude.ts +++ b/src/DatabaseServices/Entity/Extrude.ts @@ -3,6 +3,7 @@ import Flatbush from 'flatbush'; import { Box3, BoxGeometry, BufferGeometry, ExtrudeGeometry, ExtrudeGeometryOptions, Float32BufferAttribute, FrontSide, Frustum, Geometry, Group, InstancedInterleavedBuffer, InterleavedBufferAttribute, LineSegments, Material, Matrix3, Matrix4, Mesh, Object3D, Line as TLine, UVGenerator, Vector3 } from "three"; import { Line2 } from "three/examples/jsm/lines/Line2"; import { LineGeometry } from "three/examples/jsm/lines/LineGeometry"; +import { BoardCuttingForSweep } from '../../Add-on/BoardCutting/BoardCuttingForSweep'; import { arrayClone, arrayLast, arrayPushArray, arrayRemoveIf, arrayRemoveOnce, arraySortByNumber, arraySum } from "../../Common/ArrayExt"; import { CSGIntersect } from '../../Common/CSGIntersect'; import { ColorMaterial } from "../../Common/ColorPalette"; @@ -1299,6 +1300,13 @@ export class ExtrudeSolid extends Entity */ ConverToLocalGroove(target: ExtrudeSolid): ExtrudeSolid[] { + // 针对圆弧板 + if (target instanceof Board && this instanceof Board && (target.IsArcBoard || this.IsArcBoard)) + { + const boardCuttingForSweep = new BoardCuttingForSweep(this, target); + return boardCuttingForSweep.ConverToLocalGroove(); + } + if (!this.OBB.intersectsOBB(target.OBB)) return []; let n1 = this.Normal; @@ -1964,7 +1972,7 @@ export class ExtrudeSolid extends Entity /** * 计算关联拉槽,更新绘制对象(MeshGeometry和EdgeGeometry) */ - private CalcRelevanceGroove() + protected CalcRelevanceGroove() { //避免Jig实体更新,导致性能暴跌. if (!this.Id) return; @@ -1975,8 +1983,8 @@ export class ExtrudeSolid extends Entity this.GetRelevanceKnifes(knifs); //如果是切割圆弧板或刀是圆弧板,先不切割 - if (this instanceof Board && this.IsArcBoard) knifs = []; - knifs = knifs.filter(e => !(e instanceof Board && e.IsArcBoard)); + // if (this instanceof Board && this.IsArcBoard) knifs = []; + // knifs = knifs.filter(e => !(e instanceof Board && e.IsArcBoard)); if (knifs.length > 0) {