C#ATIA

↑タイトル詐欺 主にFusion360API 偶にCATIA V5 VBA(絶賛ネタ切れ中)

ポリゴンの外接円や内接円1

こちらの続きを考えてます。
速度を落とさず、Undo歴をスッキリ3 - C#ATIA

思ったより高速になったので、もうちょっと頑張っちゃいたい気持ちもあるので
テストしました。

あちらは円フィッティングした円しか作れないのですが、人によっては
外接円や内接円も欲しい様な気がしてます。
(僕が検査している時は、そんな時ありました)

そこで安易なアルゴリズムで挑戦しました。
外接円 -> フィッテイング円の中心を利用して、ポリゴン頂点の一番遠い位置を半径として円を作成
内接円 -> フィッテイング円の中心を利用して、ポリゴンの辺の一番近い位置を半径として円を作成
こんな感じです。
※numpy使ってます。試したい場合はそれなりに書き換えてください。

# Fusion360API Python script

import traceback
import adsk.fusion
import adsk.core
import inspect
import os
import sys

script_path = os.path.abspath(inspect.getfile(inspect.currentframe()))
script_dir = os.path.dirname(script_path)

if os.name == "posix":  
    sys.path.append(script_dir + "/ModulesMac")
else:
    sys.path.append(script_dir + "/ModulesWin")

try:
    import numpy as np
finally:
    del sys.path[-1]


def run(context):
    ui = adsk.core.UserInterface.cast(None)
    try:
        app: adsk.core.Application = adsk.core.Application.get()
        ui = app.userInterface
        des: adsk.fusion.Design = app.activeProduct
        root: adsk.fusion.Component = des.rootComponent

        skt: adsk.fusion.Sketch = root.sketches[0]
        lines = skt.sketchCurves.sketchLines

        # フィッティング円
        fitCir = _getFittingCircle(lines)
        drawCircle(fitCir, skt)

        # フィッティング円の中心を利用して最大の外接円
        maxCir = getMaxCircle(fitCir, lines)
        drawCircle(maxCir, skt)

        # フィッティング円を中心を利用して線との最短距離を
        # 半径とする内接円
        innerCir = getInnerCircle(fitCir, lines)
        drawCircle(innerCir, skt)

    except:
        if ui:
            ui.messageBox('Failed:\n{}'.format(traceback.format_exc()))

def getInnerCircle(
    circle: adsk.core.Circle3D,
    lines: list) -> adsk.core.Circle3D:

    app: adsk.core.Application = adsk.core.Application.get()
    mars: adsk.core.MeasureManager = app.measureManager

    center: adsk.core.Point3D = circle.center

    def getMinimumDist_Center(line: adsk.fusion.SketchLine) ->float:
        res: adsk.core.MeasureResults = mars.measureMinimumDistance(center, line)
        return res.value

    line: adsk.fusion.SketchLine
    minRadius = min([getMinimumDist_Center(line) for line in lines])

    clone: adsk.core.Circle3D = circle.copy()
    clone.radius = minRadius

    return clone


def getMaxCircle(
    circle: adsk.core.Circle3D,
    lines: list) -> adsk.core.Circle3D:

    center: adsk.core.Point3D = circle.center

    line: adsk.fusion.SketchLine
    pnts = [line.startSketchPoint.geometry for line in lines]

    maxRadius = max([center.distanceTo(p) for p in pnts])

    clone: adsk.core.Circle3D = circle.copy()
    clone.radius = maxRadius

    return clone


def drawCircle(
    circle: adsk.core.Circle3D,
    skt: adsk.fusion.Sketch):

    circles: adsk.fusion.SketchCircles = skt.sketchCurves.sketchCircles
    circles.addByCenterRadius(circle.center, circle.radius)



def _getFittingCircle(
        lines: list) -> adsk.core.Circle3D:
    # https://mori-memo.hateblo.jp/entry/2020/04/27/205437

    points = [l.startSketchPoint.geometry for l in lines]

    x = np.array([p.x for p in points])
    y = np.array([p.y for p in points])

    A = np.vstack((x, y, np.ones((len(x))))).T
    v = -(x ** 2 + y ** 2)
    u, residuals, rank, s = np.linalg.lstsq(A, v, rcond=None)

    cx_pred = u[0] / (-2)
    cy_pred = u[1] / (-2)
    r_pred = np.sqrt(cx_pred ** 2 + cy_pred ** 2 - u[2])

    return adsk.core.Circle3D.createByCenter(
        adsk.core.Point3D.create(cx_pred, cy_pred, 0),
        adsk.core.Vector3D.create(0, 0, 1),
        r_pred
    )

スケッチにポリゴンコマンドで描き、破壊し一か所をちょっと移動。
その状態でスクリプトを実行した結果はこれ。
f:id:kandennti:20210826172238p:plain
赤:フィッティング円
青:外接円っぽい奴
緑:内接円っぽい奴
それなりに出来ているような気がしないでもないです。


もうちょっと歪な形状で試すと・・・
f:id:kandennti:20210826172250p:plain
外接円もかなり怪しいし、内接円に至っては内接すらしていないと言う悲劇。
フィッティング円の中心は使えないんですね。(中学生の数学レベルのお話かな?)


三角形の外接円や内接円の中心求め方は、検索すれば幾らでもHitするんだけど、
どうなんだろう? ポリゴンを三角形に分割するのかな?