TheSwamp
Code Red => .NET => Topic started by: gile on March 23, 2011, 06:05:56 PM
-
Hi,
Trying to write a little function to return all block reference ObjectIds for a specific name I began this way:
let GetBlockReferences (db : Database) (bName : string) =
use tr = db.TransactionManager.StartTransaction()
let bt = tr.GetObject(db.BlockTableId, OpenMode.ForRead) :?> BlockTable
if bt.Has(bName) then
let btr = tr.GetObject(bt.[bName], OpenMode.ForRead) :?> BlockTableRecord
btr.GetBlockReferenceIds(true, false)
|> Seq.cast<_>
|> Seq.filter (fun id ->
let br = tr.GetObject(id, OpenMode.ForRead)
let owner = tr.GetObject(br.OwnerId, OpenMode.ForRead) :?> BlockTableRecord
owner.IsLayout)
else Seq.empty
But:
let br = tr.GetObject(id, OpenMode.ForRead)
within the the anonymous function for Seq.filter made AutoCAD crash (fatal error).
I can solve it using a nested transaction but can't understand why it's needed.
This code works:
let GetBlockReferences (db : Database) (bName : string) =
use tr = db.TransactionManager.StartTransaction()
let bt = tr.GetObject(db.BlockTableId, OpenMode.ForRead) :?> BlockTable
if bt.Has(bName) then
let btr = tr.GetObject(bt.[bName], OpenMode.ForRead) :?> BlockTableRecord
btr.GetBlockReferenceIds(true, false)
|> Seq.cast<_>
|> Seq.filter (fun id ->
use trx = db.TransactionManager.StartTransaction()
let br = trx.GetObject(id, OpenMode.ForRead)
let owner = trx.GetObject(br.OwnerId, OpenMode.ForRead) :?> BlockTableRecord
owner.IsLayout)
else Seq.empty
But I don't need to use a nested transaction in the Seq.choose function of this test command:
[<CommandMethod("test")>]
let test() =
let doc = AcAp.DocumentManager.MdiActiveDocument
let db = doc.Database
let ed = doc.Editor
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
let name = btr.Name
if name.ToUpper().Contains("_SPACE") then None else Some name)
|> Seq.iter (fun n -> ed.WriteMessage("\n{0} = {1}", n, GetBlockReferences db n |> Seq.length))
-
Hey gile, you know more than I do about this, but
could it possbly have something to do with the that sequences are evaluated lazily, and Dispose is being called.
-
I thaught about this, but it works fine in the test command...
-
From Expert F# 2.0 if it helps
Its common to implement computations that access external resources such as databases but that
return their results on demand. But this raises a difficulty: how do you manage the lifetime of the
resources for the underlying operating system connections? One solution to this is captured by use
bindings in sequence expressions:
When a use binding occurs in a sequence expression, the resource is initialized
each time a client enumerates the sequence.
The connection is closed when the client disposes of the enumerator.
For example, consider the following function that creates a sequence expression that reads the
first two lines of a file on demand:
open System.IO
let firstTwoLines(file) =
seq { use s = File.OpenText(file)
yield s.ReadLine()
yield s.ReadLine() }
Lets now create a file and a sequence that reads the first two lines of the file on demand:
> File.WriteAllLines("test1.txt", [| "Es kommt ein Schiff";
"A ship is coming" |]);;
val it : unit = ()
> let seq = firstTwoLines("test1.txt");;
val seq : seq<string>
At this point, the file hasnt yet been opened, and no lines have been read from the file. If you now
iterate the sequence expression, the file is opened, the first two lines are read, and the results are
consumed from the sequence and printed. Most important, the file has now also been closed, because
the Seq.iter aggregate operator is careful to dispose of the underlying enumerator it uses for the
sequence, which in turn disposes of the file handle generated by File.OpenText:
> seq |> Seq.iter (printfn "line = '%s'");;
line = 'Es kommt ein Schiff'
line = A ship is coming'
Chapter 9 covers sequence expressions and the more general mechanism of workflows in more
detail.
Here is something from a pdf I got if it helps
-
I think you're right with the lazyness issue.
Your reply gave me an idea: 'commit' the sequence with Seq.toArray and it seems to work.
let GetBlockReferences (db : Database) (bName : string) =
use tr = db.TransactionManager.StartTransaction()
let bt = tr.GetObject(db.BlockTableId, OpenMode.ForRead) :?> BlockTable
if bt.Has(bName) then
let btr = tr.GetObject(bt.[bName], OpenMode.ForRead) :?> BlockTableRecord
btr.GetBlockReferenceIds(true, false)
|> Seq.cast<_>
|> Seq.filter (fun id ->
let br = tr.GetObject(id, OpenMode.ForRead)
let owner = tr.GetObject(br.OwnerId, OpenMode.ForRead) :?> BlockTableRecord
owner.IsLayout)
|> Seq.toArray
else Array.empty
-
So, here's the 'final' function.
It returns an array of all ObjectIds of block references inserted in layouts for the specific name.
If the block is a dynamic block, the anonymous block references creatted from this named block are returned too.
let GetBlockReferences (db : Database) (bName : string) =
use tr = db.TransactionManager.StartTransaction()
let bt = tr.GetObject(db.BlockTableId, OpenMode.ForRead) :?> BlockTable
if bt.Has(bName) then
let btr = tr.GetObject(bt.[bName], OpenMode.ForRead) :?> BlockTableRecord
btr.GetAnonymousBlockIds()
|> Seq.cast<_>
|> Seq.map (fun id -> tr.GetObject(id, OpenMode.ForRead) :?> BlockTableRecord)
|> Seq.append (Seq.singleton btr)
|> Seq.collect (fun b ->
b.GetBlockReferenceIds(true, false)
|> Seq.cast<_>
|> Seq.filter (fun id ->
let br = tr.GetObject(id, OpenMode.ForRead)
let owner = tr.GetObject(br.OwnerId, OpenMode.ForRead) :?> BlockTableRecord
owner.IsLayout))
|> Seq.toArray
else Array.empty
A test command which select/grip all references for the specified name:
[<CommandMethod("test")>]
let test() =
let doc = AcAp.DocumentManager.MdiActiveDocument
let ed = doc.Editor
let pr = ed.GetString("\nBlock name:")
if pr.Status = PromptStatus.OK then
ed.SetImpliedSelection(GetBlockReferences doc.Database pr.StringResult)