Author Topic: Block Names and Count F#  (Read 2426 times)

0 Members and 1 Guest are viewing this topic.

Jeff H

  • Needs a day job
  • Posts: 6151
Block Names and Count F#
« on: February 05, 2011, 06:45:58 AM »
How would you put the block names and count in a string*int list


I can do it with a System.Collections.Generic.List<string * int>()

Here is some code doing that but would rather put in string*int list

Code: [Select]

let blks = new System.Collections.Generic.List<string * int>()

[<CommandMethod("IterateBlkTable")>]
 let IterateBlkTable() =

    let doc = Application.DocumentManager.MdiActiveDocument
    let db = doc.Database
    let ed = doc.Editor
   
    use trx = db.TransactionManager.StartTransaction()
    let bt = db.BlockTableId.GetObject(OpenMode.ForRead) :?> BlockTable
     
    for ids in bt do
        let btr = trx.GetObject(ids, OpenMode.ForRead) :?> BlockTableRecord
        if not btr.IsLayout then
           ed.WriteMessage(String.Format("\n{0}, {1}", btr.Name, btr.GetBlockReferenceIds(true, false).Count))     

 
[<CommandMethod("IterateBlkTable1")>]
  let IterateBlkTable1() =

     let doc = Application.DocumentManager.MdiActiveDocument
     let db = doc.Database
     let ed = doc.Editor
     
     use trx = db.TransactionManager.StartTransaction()
     let bt = db.BlockTableId.GetObject(OpenMode.ForRead) :?> BlockTable
     
     for ids in bt do
       let btr = trx.GetObject(ids, OpenMode.ForRead) :?> BlockTableRecord
       if not btr.IsLayout then
        blks.Add(btr.Name, btr.GetBlockReferenceIds(true, false).Count)
     
     trx.Commit()   

     for bksname in blks do 
      ed.WriteMessage("\n" + bksname.ToString())     
     blks.Clear()

gile

  • Gator
  • Posts: 2520
  • Marseille, France
Re: Block Names and Count F#
« Reply #1 on: February 05, 2011, 08:07:55 AM »
Hi,

Code: [Select]
let blkCount() =
    let db = Application.DocumentManager.MdiActiveDocument.Database
    use tr = db.TransactionManager.StartTransaction()
    tr.GetObject(db.BlockTableId, OpenMode.ForRead) :?> BlockTable
    |> Seq.cast
    |> Seq.choose (fun id ->
                    let btr = tr.GetObject(id, OpenMode.ForRead) :?> BlockTableRecord
                    if btr.IsLayout then None else Some (btr.Name, btr.GetBlockReferenceIds(true, false).Count))
    |> List.ofSeq
   
[<CommandMethod("Test")>]
let Test() =
    let ed = Application.DocumentManager.MdiActiveDocument.Editor
    blkCount() |> List.iter(fun (n, i) -> ed.WriteMessage("\n" + n + ": " + i.ToString()))
« Last Edit: February 05, 2011, 08:17:14 AM by gile »
Speaking English as a French Frog

kaefer

  • Guest
Re: Block Names and Count F#
« Reply #2 on: February 05, 2011, 08:13:25 AM »
How would you put the block names and count in a string*int list

Good ol' assoc list? That's immutable Map<_,_> today, or read-only IDictionary, if you have to.

If you want to store it in a list because the order is meaningful, you will likely get reverse order
(when you do it imperatively with mutable/ref cell or recursively with accumulator) or not
(recursive continuation-style).

Quote
I can do it with a System.Collections.Generic.List<string * int>()

My favorite would use higher-order functions to manipulate the underlying sequence.
If you replace the dict keyword with Seq.toList or List.ofSeq: voila.

Code: [Select]
[<CommandMethod("IterateBlkTable2")>]
let IterateBlkTable2() =

    let doc = acApp.DocumentManager.MdiActiveDocument
    let db = doc.Database
    let ed = doc.Editor
   
    use trx = db.TransactionManager.StartTransaction()
    let bt = db.BlockTableId.GetObject(OpenMode.ForRead) :?> BlockTable
   
    let blks =
        bt
        |> Seq.cast<_>
        |> Seq.map
            (fun oid ->
                trx.GetObject(oid, OpenMode.ForRead) :?> BlockTableRecord)
        |> Seq.filter (fun btr -> not btr.IsLayout)
        |> Seq.map
            (fun btr ->
                btr.Name, btr.GetBlockReferenceIds(true, false).Count)
        |> dict

    trx.Commit()   

    for bksname in blks do 
        ed.WriteMessage("\n" + bksname.ToString())

Jeff H

  • Needs a day job
  • Posts: 6151
Re: Block Names and Count F#
« Reply #3 on: February 05, 2011, 09:34:26 AM »
Just the two I was waiting for to reply.

Thanks alot guys


If you wanted the sum of all the blocks from the list would you do something like this?


Code: [Select]

let rec blocksTotalSum blist =
  match blist with
  | [] -> 0
  | (_, count)::tail ->
      let remainingSum = blocksTotalSum(tail)
      count + remainingSum




[<CommandMethod("SumOfBlocks")>]
let SumOfBlocks() =

    let doc = Application.DocumentManager.MdiActiveDocument
    let db = doc.Database
    let ed = doc.Editor
    use trx = db.TransactionManager.StartTransaction()
    let bt = db.BlockTableId.GetObject(OpenMode.ForRead) :?> BlockTable
   
    let blist =
        bt
        |> Seq.cast<_>
        |> Seq.map
            (fun oid ->
                trx.GetObject(oid, OpenMode.ForRead) :?> BlockTableRecord)
        |> Seq.filter (fun btr -> not btr.IsLayout)
        |> Seq.map
            (fun btr ->
                btr.Name, btr.GetBlockReferenceIds(true, false).Count)
        |> Seq.toList

    trx.Commit()   

    let sum = blocksTotalSum blist

    ed.WriteMessage("\n" + sum.ToString()



Jeff H

  • Needs a day job
  • Posts: 6151
Re: Block Names and Count F#
« Reply #4 on: February 05, 2011, 09:50:49 AM »
In case your wondering where this is headed for a starter project I thought drawing a pie-chart with the block names as the labels and to size the segments by number of insertions.

Should probably only test on a drawing with 3 to 10 non-dynamic blocks.


gile

  • Gator
  • Posts: 2520
  • Marseille, France
Re: Block Names and Count F#
« Reply #5 on: February 05, 2011, 09:57:49 AM »
To get the sum of all the blocks from the list:

Code: [Select]
let blocksTotalSum lst =
    lst |> List.map (fun (n, i : int) -> i) |> List.sum
Speaking English as a French Frog

gile

  • Gator
  • Posts: 2520
  • Marseille, France
Re: Block Names and Count F#
« Reply #6 on: February 05, 2011, 10:04:45 AM »
Or:

Code: [Select]
let blocksTotalSum lst =
    lst |> List.fold(fun sum (n, i) -> sum + i) 0
Speaking English as a French Frog

Jeff H

  • Needs a day job
  • Posts: 6151
Re: Block Names and Count F#
« Reply #7 on: February 05, 2011, 02:10:25 PM »
Thanks gile I need to get past chapter 3 seems like I am using the basics



kaefer

  • Guest
Re: Block Names and Count F#
« Reply #8 on: February 06, 2011, 07:03:22 AM »
In case your wondering where this is headed for a starter project I thought drawing a pie-chart with the block names as the labels and to size the segments by number of insertions.

In that case be advised that BlockTableRecord.GetBlockReferenceIds doesn't provide a meaningful metric. I'm sure you know this already; this method returns the number of all INSERTs, nested or not, including those where the parent block isn't referenced itself, i.e. after a paste.

Quote
Should probably only test on a drawing with 3 to 10 non-dynamic blocks.

The chart and series objects scale well, but legibility of the labels may be an issue.

Spoiler alert.

Thought I should try this with data binding, which is probably over the top. The data source is generated as a sequence of BlockReferences in all Layouts, stored as an array of records to persist it outside the transaction, then converted to a DataTable.

Code: [Select]
module BlockPie

open Autodesk.AutoCAD.DatabaseServices
open Autodesk.AutoCAD.EditorInput
open Autodesk.AutoCAD.Runtime
open System.Data
open System.Windows.Forms
open System.Windows.Forms.DataVisualization.Charting

type acApp = Autodesk.AutoCAD.ApplicationServices.Application

// Convert seq<'T> to DataTable
let toDataTable (data: seq<'T>) =
    let props = System.ComponentModel.TypeDescriptor.GetProperties(typeof<'T>)
    let table = new DataTable()
    for prop in props do
        table.Columns.Add(prop.Name, prop.PropertyType) |> ignore
    for item in data do
        table.Rows.Add
            [| for prop in props -> prop.GetValue item |]
        |> ignore
    table

// Make GetObject curryable
type Transaction with
    member tr.OpenForRead oid = tr.GetObject(oid, OpenMode.ForRead, false)
let map<'T> f = Seq.cast<_> >> (Seq.map f) >> Seq.cast<'T>

// Record to hold our data
type BRecord = { BlockName : string; BlockCount : int }

// Count BlockReferences in Layouts, returns BRecord[]
let blks (db: Database) =
    use tr = db.TransactionManager.StartTransaction()
    let data =
        db.BlockTableId.GetObject(OpenMode.ForRead) :?> BlockTable
        |> map<BlockTableRecord> tr.OpenForRead
        |> Seq.filter (fun btr -> btr.IsLayout)
        |> Seq.collect (map<DBObject> tr.OpenForRead)
        |> Seq.choose (function :? BlockReference as br -> Some br | _ -> None)
        |> Seq.groupBy (fun br -> br.Name)
        |> Seq.map
            (fun (name, refs) ->
                let n = Seq.length refs
                {
                    BlockName = name + "\n" + string n
                    BlockCount = n
                }
            )
        |> Array.ofSeq
    tr.Commit()
    data

// Form to hold our Chart
type MyForm(dataView, title) as this =
    inherit Form(Size = new System.Drawing.Size(640, 480) )
   
    let closeToolStripMenuItem =
        new ToolStripMenuItem(
            ShortcutKeys = (Keys.Control ||| Keys.F4),
            Text = "&Close" )
    let rightClickMenu = new ContextMenuStrip()

    let chart = new Chart(Dock = DockStyle.Fill)
    let chartArea = new ChartArea(Name = "Area1")
    let series =
        new Series(
            ChartArea = "Area1",
            ChartType = SeriesChartType.Pie,
            Font = new System.Drawing.Font("Arial", 9.f) )
    let title = new Title(title, Font = new System.Drawing.Font("Arial", 11.f))

    do
        closeToolStripMenuItem.Click |> Observable.add (fun _ -> this.Close())
        rightClickMenu.Items.Add closeToolStripMenuItem |> ignore

        chart.Titles.Add title
        chart.ChartAreas.Add chartArea
        series.Points.DataBind(dataView, "", "BlockCount", "Label=BlockName")   
        chart.Series.Add series
        this.ContextMenuStrip <- rightClickMenu
        this.Controls.Add chart

[<CommandMethod("BPie", CommandFlags.Session)>]
let bPieCmd() =
    let (name, data) =
        use doclock = acApp.DocumentManager.MdiActiveDocument.LockDocument()
        let db = acApp.DocumentManager.MdiActiveDocument.Database
        db.Filename, blks db
    let sum = data |> Array.sumBy (fun r -> r.BlockCount)
    let dt = toDataTable data
    let frm = new MyForm(dt.DefaultView, name + "\nTotal blocks: " + string sum)
    acApp.ShowModelessDialog frm
Pic:

Jeff H

  • Needs a day job
  • Posts: 6151
Re: Block Names and Count F#
« Reply #9 on: February 06, 2011, 08:18:11 AM »
Thanks Thorsten,

Good stuff!!

Here is a form with some fake data thrown in.

Code: [Select]
open System
open System.IO
open System.Drawing
open System.Windows.Forms




let mainForm = new Form(Width = 620, Height = 450, Text = "Pie Chart")

let menu = new ToolStrip()
let btnOpen = new ToolStripButton("Open")

ignore(menu.Items.Add(btnOpen))
mainForm.Controls.Add(menu)

let boxChart =
  new PictureBox
    (BackColor = Color.White, Dock = DockStyle.Fill,
     SizeMode = PictureBoxSizeMode.CenterImage)
mainForm.Controls.Add(boxChart)


let rnd = new Random()


let randomBrush() =
  let r, g, b = rnd.Next(256), rnd.Next(256), rnd.Next(256)
  new SolidBrush(Color.FromArgb(r,g,b))

let drawPieSegment(gr:Graphics, title, startAngle, occupiedAngle) =
  let br = randomBrush()
  gr.FillPie(br, 170, 70, 260, 260, startAngle, occupiedAngle)
  br.Dispose()


let fnt = new Font("Times New Roman", 11.0f)

let centerX, centerY = 300.0, 200.0
let labelDistance = 150.0

let drawLabel(gr:Graphics, title, startAngle, occupiedAngle) = 
  let ra = (Math.PI * 2.0 * float((startAngle + occupiedAngle/2)) / 360.0)
  let x = centerX + labelDistance * cos(ra)
  let y = centerY + labelDistance * sin(ra)
  let size = gr.MeasureString(title, fnt)
  let rc = new PointF(float32(x) - size.Width / 2.0f,
                      float32(y) - size.Height / 2.0f)
  gr.DrawString(title, fnt, Brushes.Black, new RectangleF(rc, size))


let drawStep(drawingFunc, gr:Graphics, sum, data) =   
   let rec drawStepUtil(data, angleSoFar) =
      match data with 
      | [] -> ()     
      | [title, value] ->       
         let angle = 360 - angleSoFar     
         drawingFunc(gr, title, angleSoFar, angle)   
      | (title, value)::tail ->
         let angle = int(float(value) / sum * 360.0)       
         drawingFunc(gr, title, angleSoFar, angle) 
         drawStepUtil(tail, angleSoFar + angle)
   drawStepUtil(data, 0)



let drawChart(data) =

   
  let sum = float(data |> List.map (fun (n, i : int) -> i) |> List.sum)
  let pieChart = new Bitmap(600, 400)
  let gr = Graphics.FromImage(pieChart)
  gr.Clear(Color.White)
  drawStep(drawPieSegment, gr, sum, data)
  drawStep(drawLabel, gr, sum, data)
  gr.Dispose()
  pieChart


let openAndDrawChart(e) =
    let pieChart = drawChart ["Label6", 3634; "Label5", 300; "Label4", 767; "Label3", 511; "Label2", 729; "Label1", 1207]
    boxChart.Image <- pieChart

[<STAThread>]
do
 
  btnOpen.Click.Add(openAndDrawChart)
  Application.EnableVisualStyles() 
  Application.Run(mainForm)