<   March 2010   >
 123456
78910111213
14151617181920
21222324252627
28293031   
Search

Link
Navigation
Recent Comments
Category

Feed
RSS v2.0 [VALID RSS!]
ATOM

Powered
Next→
08/04  大規模外部変形への道 〜 第4回 線の操作
今回は、ここまで作成してきたクラスを発展させながら、直線の交点で線分を切断する外部変形を作成したいと思います。

まず、線同士の交点は簡単な方程式の解で求められます。 行列を用いて計算した方がスマートですが、とりあえずここでは傾きaと切片bからの解法で求めます。

class JwLine
      : (略)
  # メソッド:交点
  def intersection( other )
    dx1 = @from.x - @to.x
    dy1 = @from.y - @to.y

    dx2 = other.from.x - other.to.x
    dy2 = other.from.y - other.to.y
    if dx1 == 0.0
      if dx2 == 0.0
        point = nil
      else
        x = @from.x
        a2 = dy2 / dx2
        b2 = other.to.y - other.to.x * a2
        y = a2 * x + b2
        point = JwPoint.new( x, y )
      end
    else
      a1 = dy1 / dx1
      b1 = @to.y - @to.x * a1
      if dx2 == 0.0
        x = other.from.x
        y = a1 * x + b1
        point = JwPoint.new( x, y )
      else
        a2 = dy2 / dx2
        b2 = other.to.y - other.to.x * a2
        x = - ( b1 - b2 ) / ( a1 - a2 )
        y = a1 * x + b1
        point = JwPoint.new( x, y )
      end
    end
    point
  end
end

ここで JwPoint.new(x, y) と記述しましたが、実はこのままではエラーが発生します。 点クラスの書しかメソッドを変更して、文字列と数値の両方を受け取れるようにします。

class JwPoint
      :  (略)
  def initialize( *args )
    if args.length > 0
      if args[0].is_a? String
        params = args[0].split  # pt [x] [y] を分解する
        @x = params[1].to_f
        @y = params[2].to_f
      elsif args[0].is_a? Float  # 数値の引数を処理する
        @x = args[0]
        @y = args[1]
      end
    else
      @x = 0.0
      @y = 0.0
    end
  end
      :  (略)
end


線クラスに追加した交差点は、単なる方程式の解なので、線同士が平行でない限りは交差していなくても、延長線上の交点の座標を返します。 交差する直線を操作するには、直線同士が交差しているかどうかの判定が必要になります。 こちらのアルゴリズムを参考に実装してみました。

class JwLine
      :  (略)
  # メソッド:交差判定
  def intersect?( other )
    intersect = ((@from.x - @to.x) * (other.from.y - @from.y) + 
                  (@from.y - @to.y) * (@from.x - other.from.x)) * 
                ((@from.x - @to.x) * (other.to.y - @from.y) + 
                  (@from.y - @to.y) * (@from.x - other.to.x))
    if intersect < 0.0
      intersect = ((other.from.x - other.to.x) * (@from.y - other.from.y) +
                    (other.from.y - other.to.y) * (other.from.x - @from.x)) *
                   ((other.from.x - other.to.x) * (@to.y - other.from.y) +
                    (other.from.y - other.to.y) * (other.from.x - @to.x))
    end
    ( intersect < 0.0 ? true : false )
  end
      :  (略)
end


最後に変更したクラスを利用して外部変形を作成します。 外部変形では、直線同士を交差判定します。 交差が見つかったら、交差した線のコピーを作成します。 コピーの作成には、 .clone メソッドを使用します。 これは ruby の全てのクラスで利用できます。
元の線とコピーの端点のそれぞれを交差した点に変更します。 交差した点への変更もコピーを使用します。 そのまま line1.from = point;  line2.from = pointとすると、line1 の始点座標を変更した場合に line2 の始点座標も変わってしまいます。 これは line1 と line2 が同じ実体を参照してしまうためにおこります。

require 'JwPlotFile.rb'

plot = JwPlotFile.new
plot.read
jwc = open( 'jwc_temp.txt''w' )
begin
  jwc.print "hd\n"
  line_count = plot.lines.length - 1
  i = 0
  while i <= line_count - 1 do
    j = i + 1
    while j <= line_count do
      if plot.lines[i].intersect? plot.lines[j]               # 交差判定
        point = plot.lines[i].intersection( plot.lines[j] )   # 交点を計算
        plot.lines.push plot.lines[i].clone                   # lines[i]線切断
        plot.lines.last.from = point                          #  始点を交点に
        plot.lines[i].to = point.clone                        #  終点を交点に
        plot.lines.push plot.lines[j].clone                   # lines[j]線切断
        plot.lines.last.from = point.clone                    #  始点を交点に
        plot.lines[j].to = point.clone                        #  終点を交点に
      end
      j += 1
    end
    line_count = plot.lines.length - 1                        # 線を再カウント
    i += 1
  end
  for line in plot.lines    # 線の配列を全て書出し
    line.draw jwc
  end
ensure
  jwc.close
end


あとは、バッチファイルを作成して JWW 側から呼び出して試してみてください。

REM 交点での切断
goto exe
REM #jww
REM #cd
REM #h1
REM #hc 直線を選択してください
REM #zw
REM #e
:exe
copy jwc_temp.txt temp.txt > nul
ruby -Ks JwSample3.rb jwc_temp.txt

08/01  大規模外部変形への道 〜 第3回 線を表現する
今回は線を表現するクラスを作成します。 Jw_cad の線を構成する要素は、始点・終点・レイヤ・線色・線種・線幅があります。
ここで、始点・終点の座標には、XY座標を直接持たせるのではなく、JwPoint クラスを利用します。こうすることで、点に対する動作をどこからでも同様に利用することが出来るようになります。 このように階層的にクラスを設計するのがポイントです。

require 'JwPoint.rb'

# クラス: 線
class JwLine
  attr  :from,      true
  attr  :to,        true
  attr  :color,      true
  attr  :style,     true
  attr  :thick,     true
  attr  :layer,      true

  def initialize( *args )
    @from = JwPoint.new
    @to = JwPoint.new
    if args.length > 0
      if args[0].is_a? String
        params = args[0].split  # pt [x] [y] を分解する
        @from.x = params[0].to_f
        @from.y = params[1].to_f
        @to.x = params[2].to_f
        @to.y = params[3].to_f
      end
    end
  end

  def draw( out )
    out.printf "ly%s\n"@layer  if @layer != nil
    out.printf "lc%d\n"@color  if @color != nil
    out.printf "lt%d\n"@style  if @style != nil
    out.printf "lw%d\n"@thick  if @thick != nil
    out.printf "%.10f %.11f %.10f %.11f\n"@from.x, @from.y, @to.x, @to.y
  end

  def move( offset_x, offset_y )
    @from.move( offset_x, offset_y )
    @to.move( offset_x, offset_y )
  end
end


次に線を扱えるように JwPlotFile クラスを修正します。

require 'JwPoint.rb'
require 'JwLine.rb'

# クラス: Jw_cad 外部変形
class JwPlotFile
  attr  :positions, true
  attr  :points,    true
  attr  :lines,     true

  # 初期化メソッド
  def initialize
    @points = Array.new
    @positions = Hash.new
    @lines = Array.new
  end
  
  # メソッド:読込
  def read
    while ARGF.gets
      chomp!
      case $_
          : (中略)
        when /^lc([0-9]+)/              # 線色
          line_color = $1.to_i
        when /^lt([0-9]+)/              # 線種
          line_style = $1.to_i
        when /^\s*[0-9\.\-e]+/          # 線
          @lines.push JwLine.new( $_ )
          @lines.last.color = line_color
          @lines.last.style = line_style
          @lines.last.thick = width
          @lines.last.layer = layer
      end
    end
  end
end


最後に、このクラスを利用して線と点を同時に移動する外部変形を作成します。

require 'JwPlotFile.rb'

plot = JwPlotFile.new
plot.read
jwc = open( 'jwc_temp.txt''w' )
begin
  jwc.print "hd\n"
  for item in plot.points + plot.lines
    item.move plot.positions[1].x, plot.positions[1].y
    item.draw jwc
  end
ensure
  jwc.close
end


Ruby の配列クラスでは、+を利用することで簡単に配列同士を連結できます。 また変数の中身が線と点などとクラスが違っていても、同じ名前のメソッドがあれば、同じループ変数で処理できます。

REM 線・点の移動
goto exe
REM #jww
REM #cd
REM #h1
REM #hc 直線と点を選択してください
REM #1 移動先の点をクリックしてください
REM #zw
REM #e
:exe
copy jwc_temp.txt temp.txt > nul
ruby -Ks JwSample2.rb jwc_temp.txt


07/31  大規模外部変形への道 〜 第2回 外部変形への第1歩
前回解説したプログラムは、それ単体では全く動作しません(すいません)。 今回は、一応外部変形の骨格を作成していきたいと思います。

まず、前回のソースを JwPoint.rb として保存してください。 (前回掲載したソースには不具合があります。7/30 修正分を御利用ください。)今回はまず、外部変形全体を扱うためのクラスを定義します。

require 'JwPoint.rb'

# クラス: Jw_cad 外部変形
class JwPlotFile
  attr  :positions, true
  attr  :points,    true

  # 初期化メソッド
  def initialize
    @points = Array.new
    @positions = Hash.new
  end
  
  # メソッド:読込
  def read
    while ARGF.gets
      chomp!
      case $_
        when /^pn([0-9]+)/              # 点色
          point_color = $1.to_i
          width = 0
        when /^lw([0-9]+)/              # 線幅
          width = $1.to_i
        when /^ly([0-9a-fA-F])/         # レイヤ
          layer = $1
        when /^pt/                      # 点
          @points.push JwPoint.new( $_ )
          @points.last.color = point_color
          @points.last.diameter = width   if width != nil
          @points.last.layer = layer
        when /^hp([0-9]+)/              # 指示点
          @positions$1.to_i ] = JwPoint.new( $_ )
      end
    end
  end
end

まず、前回定義した JwPoint クラスを require メソッドで取り込みます。
次に JwPlotFile クラスの初期化で、実点を格納する配列 @points と指示点を格納する連想配列 @positions を定義しておきます。
実際に jwc_temp.txt を読み込む read メソッドでは、1行ずつ読み込みながら、実点を示す pt 〜 が出てきたところで、JwPoint クラスを変数として生成しながら配列に追加していきます。
ここで、JwPoint.new を実行すると、JwPoint クラスで定義した initialize メソッドが実行される点に注意してください。

連想配列の説明に関しては、以前の講座でもしましたが、Jw_cad の指示点は番号が連続でない場合もありますので、配列ではなく連想配列にしました。

以上のコードを JwPlotFile.rb として保存した上で、実際の外部変形呼び出し部分を記述します。

require 'JwPlotFile.rb'

plot = JwPlotFile.new
plot.read
jwc = open( 'jwc_temp.txt''w' )
begin
  jwc.print "hd\n"
  for point in plot.points
    point.move plot.positions[1].x, plot.positions[1].y
    point.draw jwc
  end
ensure
  jwc.close
end


このように処理の流れが、配列のループ〜移動〜書出しのように整然と記述できます。 これを JwSample1.rb として、バッチファイルから呼び出しします。

REM JwSample1.bat  点の移動
goto exe
REM #jww
REM #cd
REM #h1
REM #hc 点を選択してください
REM #1 移動先の点をクリックしてください
REM #zw
REM #e
:exe
copy jwc_temp.txt temp.txt > nul
ruby -Ks JwSample1.rb temp.txt


ここまでの流れでは、こんなに簡単な処理になんだか複雑なことをするような印象を受けたのではないでしょうか? この手法は、プログラムの複雑さが増すごとに効果が高くなり、また一度書いたコードの再利用が簡単になるので、慣れるに従って開発の手間が省かれていきます。

07/25  大規模外部変形への道 〜 第1回 点を表現する
複雑なプログラムを開発する第一歩は、データや関数の表現をなるべく統一的に扱うことで、読みやすさを向上し、プログラムが煩雑になるのを防ぐことにあります。
Ruby では、[対象データ].[動作]のように主語〜述語の並びに記述する方法が可能なので、違うタイプのデータに対して、同じ機能は同じ関数名(メソッド)を利用するといった統一的な操作が可能です。

具体的に、線・点・円を一定量移動するプログラムは:
    line.move offset_x, offset_y
    point.move offset_x, offset_y
    circle.move offset_x, offset_y

のように記述できます。


では、まず点のデータを表現します。 Jww の点データはご存知のとおり、座標・レイヤ・点色・点の大きさがあります。 データ表現にはクラスという単位を利用します。
    # CLASS: 点
    class JwPoint
        attr :x,        true
        attr :y,        true
        attr :layer,    true
        attr :color,    true
        attr :diameter, true
    end

このように点のクラスを記述して、その要素を attr(アトリビュート)で定義します。 attr 行の末尾の true は、クラスの外部から要素データを書き換え可能にするかどうかのフラグです。 false にすると読取専用になります。

次に点データに対する動作を記述していきます。 動作の記述には、メソッドを使いますが、class定義の中に記述すると、点データのみに対するメソッドとして定義されますので、冒頭に解説したとおり、同じ名前のメソッドを違うクラスに記述できるようになります。
ここでは、jwc_temp.txt からのデータ設定・移動・書出しの3つの動作を記述します。
class JwPoint
      : (中略)
  def initialize( *args )
    if args.length > 0
      if args[0].is_a? String
        params = args[0].split  # pt [x] [y] を分解する
        @x = params[1].to_f
        @y = params[2].to_f
      end
    else
      @x = 0.0
      @y = 0.0
    end
  end

  def draw( out )
    out.printf "ly%s\n"@layer if @layer != nil
    out.printf "pn%d\n"@color if @color != nil
    out.printf "lw%d\n"@diameter  if @diameter != nil
    out.printf "pt %.10f %.11f\n"@x@y
  end

  def move( offset_x, offset_y )
    @x += offset_x
    @y += offset_y
  end
end

ここで、クラス内で attr で定義した変数(メンバ)にアクセスするには、冒頭に @ をつけて記述します。 また後述しますがデータ設定の initialize メソッドは特殊で、クラスを変数として定義するときに暗黙に呼び出されます。

07/25  大規模外部変形への道 〜 序章
外部変形は、実はシンプルなものが汎用性が高く、使用頻度も高いというのが定説です。
しかし、機能としてのシンプルさとプログラムの複雑さは決してイコールではありません。 プログラムが大規模かつ複雑になると例え単独の開発者といえども、そのメインテナンスは容易ではありません。

プログラムの複雑性を改善するには:
 1. 読みやすさ
 2. 再利用の容易さ
の2点が重要です。 読み易さは、修正・変更の容易さにつながりますし、再利用性の容易さは次の外部変形の開発の時間を大幅に短縮してくれます。

幸いにして、以前外部変形講座でとりあげた Ruby というスクリプト言語は設計も新しく、大規模なプログラミングの複雑性を改善する様々な仕組が実装されています。

今後、不定期ではありますが、Ruby の仕組を利用して外部変形を作成する際のフレームワークを作成していこうと思います。

[PR] nTC_[ I[_[ TCY {I  ]ョ pT[o[ v[g CZ[    sY cコ Mq AXxXg rpO m AXxXg dXg[u  s sY TV{[h nTC_[    m pCvH {  GRZ  [g  ~l I[_[J[e [ [I nTC_[  s GXe lC    AXxXg sZzミ z[y[W