Kean专题(16)—Solid Modeling
本帖最后由 作者 于 2009-9-14 17:40:50 编辑http://through-the-interface.typepad.com/through_the_interface/solid_modeling/008
一、创建扫掠曲面
May 22, 2
Sweeping an AutoCAD surface using .NET
AutoCAD 2007 introduced more advanced solid & surface modeling tools. This post takes a look at how to generate one particular type of surface: a SweptSurface, which is created by sweeping a profile (which could be a region, a planar surface or a curve) through a particular path (which must be a curve).
The below C# code shows how to sweep an object along a curved path to create a surface. Our SAP (for SweepAlongPath) command doesn't provide all the options of the standard SWEEP command, as the point is to show how to do this programmatically, not to duplicate standard AutoCAD functionality.
We're creating a SweptSurface in our code: it's also possible to sweep a similar entity along a path to create a Solid3d, but at the time of writing this is only exposed through ObjectARX (AcDb3dSolid::createSweptSolid()). If you have a strong need to create swept solids in AutoCAD using .NET, please send me an email.using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.Geometry;
namespace SolidCreation
{
public class Commands
{
public void SweepAlongPath()
{
Document doc =
Application.DocumentManager.MdiActiveDocument;
Database db = doc.Database;
Editor ed = doc.Editor;
// Ask the user to select a region to extrude
PromptEntityOptions peo1 =
new PromptEntityOptions(
"\nSelect profile or curve to sweep: "
);
peo1.SetRejectMessage(
"\nEntity must be a region, curve or planar surface."
);
peo1.AddAllowedClass(
typeof(Region), false);
peo1.AddAllowedClass(
typeof(Curve), false);
peo1.AddAllowedClass(
typeof(PlaneSurface), false);
PromptEntityResult per =
ed.GetEntity(peo1);
if (per.Status != PromptStatus.OK)
return;
ObjectId regId = per.ObjectId;
// Ask the user to select an extrusion path
PromptEntityOptions peo2 =
new PromptEntityOptions(
"\nSelect path along which to sweep: "
);
peo2.SetRejectMessage(
"\nEntity must be a curve."
);
peo2.AddAllowedClass(
typeof(Curve), false);
per = ed.GetEntity(peo2);
if (per.Status != PromptStatus.OK)
return;
ObjectId splId = per.ObjectId;
// Now let's create our swept surface
Transaction tr =
db.TransactionManager.StartTransaction();
using (tr)
{
try
{
Entity sweepEnt =
tr.GetObject(regId, OpenMode.ForRead) as Entity;
Curve pathEnt =
tr.GetObject(splId, OpenMode.ForRead) as Curve;
if (sweepEnt == null || pathEnt == null)
{
ed.WriteMessage(
"\nProblem opening the selected entities."
);
return;
}
// We use a builder object to create
// our SweepOptions
SweepOptionsBuilder sob =
new SweepOptionsBuilder();
// Align the entity to sweep to the path
sob.Align =
SweepOptionsAlignOption.AlignSweepEntityToPath;
// The base point is the start of the path
sob.BasePoint = pathEnt.StartPoint;
// The profile will rotate to follow the path
sob.Bank = true;
// Now generate the surface...
SweptSurface ss =
new SweptSurface();
ss.CreateSweptSurface(
sweepEnt,
pathEnt,
sob.ToSweepOptions()
);
// ... and add it to the modelspace
BlockTable bt =
(BlockTable)tr.GetObject(
db.BlockTableId,
OpenMode.ForRead
);
BlockTableRecord ms =
(BlockTableRecord)tr.GetObject(
bt,
OpenMode.ForWrite
);
ms.AppendEntity(ss);
tr.AddNewlyCreatedDBObject(ss, true);
tr.Commit();
}
catch
{ }
}
}
}
}
Here's an image of a very simple drawing I used to demonstrate the function. I started by creating a helix, and then copied it, essentially creating my two paths. I then drew a tiny circle at the end point of the first helix (not aligned to the path in any way - just flat in the World UCS - as the SweepOptions we choose will ask that the profile to be aligned automatically to the path), and drew a simple "S"-shaped spline at the end point of the second helix (I drew the spline elsewhere and moved it using the mid-point as a base point, selecting the end of the helix as the destination).
So we end up with two paths (both helixes) and their respective non-aligned profiles (one a circle, the other a spline):
I then ran the SAP command twice, selecting one of the profiles and its respective path each time. Here's the 2D wireframe view of what was created:
To see the geometry better, I then changed to use the conceptual visual style and orbitted around to get a better 3D view:
Update
As mentioned in this post, Solid3d.CreateSweptSolid has now been implemented in AutoCAD 2010.
二、通过三维对象创建剪切平面的方式创建截面对象
May 26, 2008
Sectioning an AutoCAD solid using .NET
In the last post we saw how to access some of the 3D modeling functionality introduced in AutoCAD 2007. This post continues that theme, by looking at how to section a Solid3d object programmatically inside AutoCAD. Thanks to Wayne Brill, from DevTech Americas, for providing the original code that inspired this post.
Here's the C# code:
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.Geometry;
using System;
namespace SolidSection
{
public class Commands
{
public void SectionSolid()
{
Document doc =
Application.DocumentManager.MdiActiveDocument;
Database db = doc.Database;
Editor ed = doc.Editor;
// Ask the user to select an entity to section
PromptEntityOptions peo =
new PromptEntityOptions(
"\nSelect entity to section: "
);
peo.SetRejectMessage(
"\nEntity must be a 3D solid, " +
"surface, body or region."
);
peo.AddAllowedClass(typeof(Solid3d), false);
peo.AddAllowedClass(
typeof(Autodesk.AutoCAD.DatabaseServices.Surface),
false
);
peo.AddAllowedClass(typeof(Body), false);
peo.AddAllowedClass(typeof(Region), false);
PromptEntityResult per =
ed.GetEntity(peo);
if (per.Status != PromptStatus.OK)
return;
ObjectId entId =
per.ObjectId;
// Ask the user to define a section plane
Point3dCollection pts =
new Point3dCollection();
PromptPointResult ppr =
ed.GetPoint("\nPick first point for section: ");
if (ppr.Status != PromptStatus.OK)
return;
pts.Add(ppr.Value);
PromptPointOptions ppo =
new PromptPointOptions(
"\nPick end point for section: "
);
ppo.BasePoint = ppr.Value;
ppo.UseBasePoint = true;
ppr =
ed.GetPoint(ppo);
if (ppr.Status != PromptStatus.OK)
return;
pts.Add(ppr.Value);
// Ask what type of section to create
PromptKeywordOptions pko =
new PromptKeywordOptions(
"Enter section type "
);
pko.AllowNone = true;
pko.Keywords.Add("2D");
pko.Keywords.Add("3D");
pko.Keywords.Add("Live");
pko.Keywords.Default = "3D";
PromptResult pkr =
ed.GetKeywords(pko);
if (pkr.Status != PromptStatus.OK)
return;
SectionType st;
if (pkr.StringResult == "2D")
st = SectionType.Section2d;
else if (pkr.StringResult == "Live")
st = SectionType.LiveSection;
else // pkr.StringResult == "3D"
st = SectionType.Section3d;
// Now we're ready to do the real work
Transaction tr =
db.TransactionManager.StartTransaction();
using (tr)
{
try
{
BlockTable bt =
(BlockTable)tr.GetObject(
db.BlockTableId,
OpenMode.ForRead
);
BlockTableRecord ms =
(BlockTableRecord)tr.GetObject(
bt,
OpenMode.ForWrite
);
// Now let's create our section
Section sec =
new Section(pts, Vector3d.ZAxis);
sec.State = SectionState.Plane;
// The section must be added to the drawing
ObjectId secId =
ms.AppendEntity(sec);
tr.AddNewlyCreatedDBObject(sec, true);
// Set up some of its direct properties
sec.SetHeight(
SectionHeight.HeightAboveSectionLine,
3.0
);
sec.SetHeight(
SectionHeight.HeightBelowSectionLine,
1.0
);
// ... and then its settings
SectionSettings ss =
(SectionSettings)tr.GetObject(
sec.Settings,
OpenMode.ForWrite
);
// Set our section type
ss.CurrentSectionType = st;
// We only set one additional option if "Live"
if (st == SectionType.LiveSection)
sec.EnableLiveSection(true);
else
{
// Non-live (i.e. 2D or 3D) settings
ObjectIdCollection oic =
new ObjectIdCollection();
oic.Add(entId);
ss.SetSourceObjects(st, oic);
if (st == SectionType.Section2d)
{
// 2D-specific settings
ss.SetVisibility(
st,
SectionGeometry.BackgroundGeometry,
true
);
ss.SetHiddenLine(
st,
SectionGeometry.BackgroundGeometry,
false
);
}
else if (st == SectionType.Section3d)
{
// 3D-specific settings
ss.SetVisibility(
st,
SectionGeometry.ForegroundGeometry,
true
);
}
// Finish up the common 2D/3D settings
ss.SetGenerationOptions(
st,
SectionGeneration.SourceSelectedObjects |
SectionGeneration.DestinationFile
);
}
// Open up the main entity
Entity ent =
(Entity)tr.GetObject(
entId,
OpenMode.ForRead
);
// Generate the section geometry
Array flEnts, bgEnts, fgEnts, ftEnts, ctEnts;
sec.GenerateSectionGeometry(
ent,
out flEnts,
out bgEnts,
out fgEnts,
out ftEnts,
out ctEnts
);
// Add the geometry to the modelspace
// (start by combining the various arrays,
// so we then have one loop, not four)
int numEnts =
flEnts.Length + fgEnts.Length +
bgEnts.Length + ftEnts.Length +
ctEnts.Length;
// Create the appropriately-sized array
Array ents =
Array.CreateInstance(
typeof(Entity),
numEnts
);
// Copy across the contents of the
// various arrays
int index = 0;
flEnts.CopyTo(ents, index);
index += flEnts.Length;
fgEnts.CopyTo(ents, index);
index += fgEnts.Length;
bgEnts.CopyTo(ents, index);
index += bgEnts.Length;
ftEnts.CopyTo(ents, index);
index += ftEnts.Length;
ctEnts.CopyTo(ents, index);
// Our single loop to add entities
foreach (Entity ent2 in ents)
{
ms.AppendEntity(ent2);
tr.AddNewlyCreatedDBObject(ent2, true);
}
tr.Commit();
}
catch (System.Exception ex)
{
ed.WriteMessage(
"\nException: " + ex.Message
);
}
}
}
}
}
To see the results of the various options in the SS command, I created three identical spheres in an empty drawing:
I then used the SS command, selecting each sphere in turn and selecting a similar section line for each (as close as I could get without measuring), choosing, of course, a different command option each time (2D, 3D and Live, from left to right):
Orbitting this view, we see the section planes for each sphere:
The objects we've added to the drawing for the two left-hand sections are basic (2D or 3D, depending) geometry. The third, however, includes a section object:
A quick note on the code at the end which adds the various generated geometry to the drawing: in order to avoid having multiple foreach loops (one for each of flEnts, fgEnts, bgEnts, ftEnts & ctEnts), I opted to create an über-array which then gets populated by the contents of each of the other lists. This simple exercise was a pain in C#, as you can see from the code. In fact, having five separate loops could probably be considered less ugly, depending on your perspective. This is the kind of operation that's a breeze in a language like F#, and, with hindsight, I probably should have chosen F# from the beginning for just that reason. Maybe I'll throw an F# version together for comparison's sake.
三、判断文件是二维还是三维
July 09, 2008
Testing whether an AutoCAD drawing is 2D or 3D using .NET
This post demonstrates a simple check for whether a drawing is two or three dimensional. The code is almost embarrassingly simple, but then the question is significant and in the absence of a "Is3D" property on the Database object this is likely to prove useful for people.
So how do we check whether a drawing is 3D? The quick answer is that in most circumstances the EXTMAX system variable will have a non-zero Z value for a 3D drawing. There are potential situations where this might not be true (and EXTMAX doesn't reflect the 3D nature of certain geometry), but given the likelihood that any real-world 3D model includes a variety of geometry, it's pretty safe to rely upon. The alternative is to iterate through and test geometry, but checking EXTMAX is quicker, by far, and the alternative should only by needed if you find a particular scenario that EXTMAX doesn't address.
Here's some C# code that tells us whether a drawing is 2D or 3D:
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.Runtime;
namespace NumberOfDimensions
{
public class Commands
{
static public void CheckWhether3D()
{
Document doc =
Application.DocumentManager.MdiActiveDocument;
Database db = doc.Database;
Editor ed = doc.Editor;
ed.WriteMessage(
"\nDrawing is {0}.",
(IsDrawing3D(db) ? "3D" : "2D")
);
}
private static bool IsDrawing3D(Database db)
{
return (db.Extmax.Z > Tolerance.Global.EqualPoint);
}
}
}Here's what happens when we call the IS3D command on a fresh drawing, after we've drawn a 2D line and then after we've drawn a tiny sphere:
Command: IS3D
Drawing is 2D.
Command: LINE
Specify first point: 0,0,0
Specify next point or : @10,10
Specify next point or :
Command: IS3D
Drawing is 2D.
Command: SPHERE
Specify center point or : 0,0,0
Specify radius or : 0.0001
Command: IS3D
Drawing is 3D.
本帖最后由 作者 于 2009-9-17 10:35:31 编辑
四、使用AutoCad2009新的API遍历三维实体的边界
September 01, 2008
Traversing a 3D solid's brep using AutoCAD 2009's new .NET API
In a recent webcast, Gopinath Taget, from our DevTech Americas team, showed how to use the Brep API from a .NET application: something that was made possible in AutoCAD 2009. The Brep API in AutoCAD allows you to traverse theboundary representation of a Solid3d object. Without going into specifics - as this isn't really an area of AutoCAD I've had much reason to use, over the years - I went ahead and took the sample Gopi showed in his webcast and modified it for the purposes of this blog.
The following C# code traverses the Brep of a selected Solid3d, dumping the information to the command-line. It uses the technique shown in this previous post to retrieve the type of solid we're dealing with via COM.
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.BoundaryRepresentation;
using Autodesk.AutoCAD.Interop.Common;
using BrFace =
Autodesk.AutoCAD.BoundaryRepresentation.Face;
namespace BRepTraversal
{
public class Commands
{
static public void TraverseBRep()
{
Document doc =
Application.DocumentManager.MdiActiveDocument;
Database db = doc.Database;
Editor ed = doc.Editor;
Transaction tr =
db.TransactionManager.StartTransaction();
using (tr)
{
try
{
// Prompt for selection of a solid to be traversed
PromptEntityOptions prEntOpt =
new PromptEntityOptions(
"\nSelect a 3D solid:"
);
prEntOpt.SetRejectMessage(
"\nMust be a 3D solid."
);
prEntOpt.AddAllowedClass(typeof(Solid3d), true);
PromptEntityResult prEntRes =
ed.GetEntity(prEntOpt);
ObjectId[] objIds = { prEntRes.ObjectId };
Solid3d sol =
(Solid3d)tr.GetObject(
prEntRes.ObjectId,
OpenMode.ForRead
);
// Use COM to get the solid's type
Acad3DSolid oSol = (Acad3DSolid)sol.AcadObject;
ed.WriteMessage(
"\nSolid type: {0}",
oSol.SolidType
);
oSol = null;
// Build the BRep topology object to traverse
Brep brp = new Brep(sol);
using (brp)
{
int cmpCnt = 0;
// Get all the Complexes which are primary BRep
// elements and represent a conceptual topological
// entity of connected shell boundaries.
foreach (Complex cmp in brp.Complexes)
{
ed.WriteMessage(
"\nComplex number {0}",
++cmpCnt
);
// Get all the shells within a complex. Shells
// are secondary BRep entities that correspond
// to a collection of neighboring surfaces on a
// solid
int shlCnt = 0;
foreach (Shell shl in cmp.Shells)
{
ed.WriteMessage(
"\n Shell number {0} [{1}]",
++shlCnt,
shl.ShellType
);
// Get all the faces in a shell. Faces are
// primary BRep topological entities that
// directly correspond to face subentities on
// AutoCAD entities like solid, region and body
int fceCnt = 0;
foreach (BrFace fce in shl.Faces)
{
ed.WriteMessage(
"\n Face number {0}",
++fceCnt
);
// Get all the boundary loops within a face
// (Secondary BRep entities and no corresponding
// AutoCAD entities/subentities)
try
{
int lpCnt = 0;
foreach (BoundaryLoop lp in fce.Loops)
{
ed.WriteMessage(
"\n Loop number {0} [{1}]",
++lpCnt,
lp.LoopType
);
// Get all the Edges in a loop (Edges are
// primary BRep entities and correspond to
// Geometric entities). Output the
int edgCnt = 0;
foreach (Edge edg in lp.Edges)
{
ed.WriteMessage(
"\n Edge number {0}: " +
"\n Vertex 1: {1}" +
"\n Vertex 2: {2}",
++edgCnt,
edg.Vertex1.Point,
edg.Vertex2.Point
);
}
}
}
catch
{
ed.WriteMessage(
"\n Problem getting loops/edges:" +
" object is probably unbounded " +
"(e.g. a sphere or a torus)."
);
}
}
}
}
}
tr.Commit();
}
catch (System.Exception ex)
{
ed.WriteMessage(
"\nException during traversal: {0}",
ex.Message
);
}
}
}
}
}
Here's what happens when we run the TBR command against a cylinder:
Command:TBR
Select a 3D solid:
Solid type: Cylinder
Complex number 1
Shell number 1
Face number 1
Loop number 1
Edge number 1:
Vertex 1: (15.0005647466013,4.38174252440491,0)
Vertex 2: (15.0005647466013,4.38174252440491,0)
Loop number 2
Edge number 1:
Vertex 1: (15.0005647466013,4.38174252440491,5.78051528967497)
Vertex 2: (15.0005647466013,4.38174252440491,5.78051528967497)
Face number 2
Loop number 1
Edge number 1:
Vertex 1: (15.0005647466013,4.38174252440491,0)
Vertex 2: (15.0005647466013,4.38174252440491,0)
Face number 3
Loop number 1
Edge number 1:
Vertex 1: (15.0005647466013,4.38174252440491,5.78051528967497)
Vertex 2: (15.0005647466013,4.38174252440491,5.78051528967497)One point to note: in the above code I've used a try-catch block around the code to get the loops of a face and the edges of a loop. This is because the GetEnumerator() call on either a loop or an edge (which gets called implicitly by the foreach statement to loop through their respective enumerable objects) can throw an exception in the case where an object is unbounded. Here's an excerpt from the .NET reference material (currently residing in the ObjectARX Reference, which is part of the ObjectARX SDK):
A topological object may be unbounded (that is, it may have no lower dimensional bounding topology) only in the following cases:
* A closed surface, which is intrinsically bounded in both the u and v directions (such as a full torus or sphere), is represented by a face that has no loop boundaries.
* A closed curve, which is intrinsically bounded (such as a full circle or ellipse), is represented by an edge that has coincident start and end vertices.
For example, here's what happens when we run the TBR command against a torus:
Command:TBR
Select a 3D solid:
Solid type: Torus
Complex number 1
Shell number 1
Face number 1
Problem getting loops/edges: object is probably unbounded (e.g. a sphere or a torus).So we have, at least, detected the case, even if we have to deal with the overhead of catching an exception rather than checking a property for the number of Loops (something I couldn't work out how to do).
<p>四、在三维实体上选择最接近的面</p><p>January 20, 2009<br/>Selecting the nearest face of an AutoCAD solid using .NET</p><p>This post has come out of an interesting discussion I had with Jim Cameron at the ADN party at AU 2008. He mentioned an idea, which he kindly later reminded me of by email, which was to develop an AutoCAD equivalent for Inventor's LookAt functionality. I didn't know about LookAt before this discussion, but it seems it allows you to look at a particular face: you pick a face and it rotates the view and zooms in to centre it on the screen.</p><p>Rather than try to attack the whole problem at once, this post tackles selecting a face (which is slightly more complicated than perhaps it might be) and in a future post I'll hopefully manage to post some code to perform the view change.</p><p>Here's the C# code:</p><p>using System.Collections.Generic;</p><p>using Autodesk.AutoCAD.ApplicationServices;</p><p>using Autodesk.AutoCAD.DatabaseServices;</p><p>using Autodesk.AutoCAD.EditorInput;</p><p>using Autodesk.AutoCAD.Runtime;</p><p>using Autodesk.AutoCAD.Geometry;</p><p>using Autodesk.AutoCAD.GraphicsInterface;</p><p>using Autodesk.AutoCAD.BoundaryRepresentation;</p><p>using BrFace =</p><p> Autodesk.AutoCAD.BoundaryRepresentation.Face;</p><p>using BrException =</p><p> Autodesk.AutoCAD.BoundaryRepresentation.Exception;</p><p>namespace LookAtFace</p><p>{</p><p> public class Commands</p><p> {</p><p> // Keep a list of trhe things we've drawn</p><p> // so we can undraw them</p><p> List<Drawable> _drawn = new List<Drawable>();</p><p> </p><p> public void PickFace()</p><p> {</p><p> Document doc =</p><p> Application.DocumentManager.MdiActiveDocument;</p><p> Database db = doc.Database;</p><p> Editor ed = doc.Editor;</p><p> ClearDrawnGraphics();</p><p> PromptEntityOptions peo =</p><p> new PromptEntityOptions(</p><p> "\nSelect face of solid:"</p><p> );</p><p> peo.SetRejectMessage("\nMust be a 3D solid.");</p><p> peo.AddAllowedClass(typeof(Solid3d), false);</p><p> PromptEntityResult per =</p><p> ed.GetEntity(peo);</p><p> if (per.Status != PromptStatus.OK)</p><p> return;</p><p> Transaction tr =</p><p> db.TransactionManager.StartTransaction();</p><p> using (tr)</p><p> {</p><p> Solid3d sol =</p><p> tr.GetObject(per.ObjectId, OpenMode.ForRead)</p><p> as Solid3d;</p><p> if (sol != null)</p><p> {</p><p> Brep brp = new Brep(sol);</p><p> using (brp)</p><p> {</p><p> // We're going to check interference between our</p><p> // solid and a line we're creating between the</p><p> // picked point and the user (we use the view</p><p> // direction to decide in which direction to</p><p> // draw the line)</p><p> Point3d dir =</p><p> (Point3d)Application.GetSystemVariable("VIEWDIR");</p><p> Point3d picked = per.PickedPoint,</p><p> nearerUser =</p><p> per.PickedPoint - (dir - Point3d.Origin);</p><p> // Two hits should be enough (in and out)</p><p> const int numHits = 2;</p><p> // Create out line</p><p> Line3d ln = new Line3d(picked, nearerUser);</p><p> Hit[] hits = brp.GetLineContainment(ln, numHits);</p><p> ln.Dispose();</p><p> if (hits == null || hits.Length < numHits)</p><p> return;</p><p> // Set the shortest distance to something large</p><p> // and the index to the first item in the list</p><p> double shortest = (picked - nearerUser).Length;</p><p> int found = 0;</p><p> // Loop through and check the distance to the</p><p> // user (the depth of field).</p><p> for (int idx = 0; idx < numHits; idx++)</p><p> {</p><p> Hit hit = hits;</p><p> double dist = (hit.Point - nearerUser).Length;</p><p> if (dist < shortest)</p><p> {</p><p> shortest = dist;</p><p> found = idx;</p><p> }</p><p> }</p><p> // Once we have the nearest point to the screen,</p><p> // use that one to get the containing curves</p><p> List<Curve3d> curves = new List<Curve3d>();</p><p> if (CheckContainment(</p><p> ed,</p><p> brp,</p><p> hits.Point,</p><p> ref curves</p><p> )</p><p> )</p><p> {</p><p> // If we get some back, get drawables for them and</p><p> // pass them through to the transient graphics API</p><p> TransientManager tm =</p><p> TransientManager.CurrentTransientManager;</p><p> IntegerCollection ic = new IntegerCollection();</p><p> foreach (Curve3d curve in curves)</p><p> {</p><p> Drawable d = GetDrawable(curve);</p><p> tm.AddTransient(</p><p> d,</p><p> TransientDrawingMode.DirectTopmost,</p><p> 0,</p><p> ic</p><p> );</p><p> _drawn.Add(d);</p><p> }</p><p> }</p><p> }</p><p> }</p><p> tr.Commit();</p><p> }</p><p> }</p><p> private void ClearDrawnGraphics()</p><p> {</p><p> // Clear any graphics we've drawn with the transient</p><p> // graphics API, then clear the list</p><p> TransientManager tm =</p><p> TransientManager.CurrentTransientManager;</p><p> IntegerCollection ic = new IntegerCollection();</p><p> foreach (Drawable d in _drawn)</p><p> {</p><p> tm.EraseTransient(d, ic);</p><p> }</p><p> _drawn.Clear();</p><p> }</p><p> private Drawable GetDrawable(Curve3d curve)</p><p> {</p><p> // We could support multiple curve types here, but for</p><p> // now let's just return a line approximating it</p><p> Line ln = new Line(curve.StartPoint, curve.EndPoint);</p><p> ln.ColorIndex = 1;</p><p> return ln;</p><p> }</p><p> private static bool CheckContainment(</p><p> Editor ed,</p><p> Brep brp,</p><p> Point3d pt,</p><p> ref List<Curve3d> curves</p><p> )</p><p> {</p><p> bool res = false;</p><p> // Use the BRep API to get the lowest level</p><p> // container for the point</p><p> PointContainment pc;</p><p> BrepEntity be =</p><p> brp.GetPointContainment(pt, out pc);</p><p> using (be)</p><p> {</p><p> // Only if the point is on a boundary...</p><p> if (pc == PointContainment.OnBoundary)</p><p> {</p><p> // And only if the boundary is a face...</p><p> BrFace face = be as BrFace;</p><p> if (face != null)</p><p> {</p><p> // ... do we attempt to do something</p><p> try</p><p> {</p><p> foreach (BoundaryLoop bl in face.Loops)</p><p> {</p><p> // We'll return a curve for each edge in</p><p> // the containing loop</p><p> foreach (Edge edge in bl.Edges)</p><p> {</p><p> curves.Add(edge.Curve);</p><p> }</p><p> }</p><p> res = true;</p><p> }</p><p> catch (BrException)</p><p> {</p><p> res = false;</p><p> }</p><p> }</p><p> }</p><p> }</p><p> return res;</p><p> }</p><p> }</p><p>}</p><p>A few comments on the implementation:</p><p> * We use the standard Editor.GetEntity() selection method - it gives us the ObjectId of the selected Solid3d but also the point that was picked.<br/> * Using this point and the view direction, we can then draw a line (which we make as big as the diagonal of the solid's bounding box, which should be large enough) from that point in the direction of the user.<br/> * The Boundary Representation (BRep) API allows us to determine how this line intersects the solid: we select the intersection nearest the screen, as presumably that's the one the user was intending to pick.<br/> * We will then use the BRep API to test the solid to see whether the point is contained by (or - and this is more likely - on) the solid, and it very helpfully provides us with the lowest-level topological entity that contains the point (which we hope to be a face).<br/> * The BRep API will throw an exception when traversing (typically during calls to GetEnumerator() for various collections) for a couple of unbounded solid-types (spheres, etc.) as we traverse: in this case we simply abort the containment checking operation.<br/> * We use the Transient Graphics API to display the edges of the selected face. Right now we just draw lines for each curve - which will be wrong for anything with arcs or circles, for instance - but at this stage we don't care a great deal about the graphics we're drawing - this is really just to make sure we're approximately accurate, and later we'll do something more intelligent with the edges we get back for the selected face.</p><p>Here's what happens when we use the PICKFACE command to select first one face and then another of a simple box:</p> 本帖最后由 作者 于 2009-9-17 10:35:59 编辑
五、在三维实体上选择最接近的面
January 20, 2009
Selecting the nearest face of an AutoCAD solid using .NET
This post has come out of an interesting discussion I had with Jim Cameron at the ADN party at AU 2008. He mentioned an idea, which he kindly later reminded me of by email, which was to develop an AutoCAD equivalent for Inventor's LookAt functionality. I didn't know about LookAt before this discussion, but it seems it allows you to look at a particular face: you pick a face and it rotates the view and zooms in to centre it on the screen.
Rather than try to attack the whole problem at once, this post tackles selecting a face (which is slightly more complicated than perhaps it might be) and in a future post I'll hopefully manage to post some code to perform the view change.
Here's the C# code:
using System.Collections.Generic;
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.GraphicsInterface;
using Autodesk.AutoCAD.BoundaryRepresentation;
using BrFace =
Autodesk.AutoCAD.BoundaryRepresentation.Face;
using BrException =
Autodesk.AutoCAD.BoundaryRepresentation.Exception;
namespace LookAtFace
{
public class Commands
{
// Keep a list of trhe things we've drawn
// so we can undraw them
List<Drawable> _drawn = new List<Drawable>();
public void PickFace()
{
Document doc =
Application.DocumentManager.MdiActiveDocument;
Database db = doc.Database;
Editor ed = doc.Editor;
ClearDrawnGraphics();
PromptEntityOptions peo =
new PromptEntityOptions(
"\nSelect face of solid:"
);
peo.SetRejectMessage("\nMust be a 3D solid.");
peo.AddAllowedClass(typeof(Solid3d), false);
PromptEntityResult per =
ed.GetEntity(peo);
if (per.Status != PromptStatus.OK)
return;
Transaction tr =
db.TransactionManager.StartTransaction();
using (tr)
{
Solid3d sol =
tr.GetObject(per.ObjectId, OpenMode.ForRead)
as Solid3d;
if (sol != null)
{
Brep brp = new Brep(sol);
using (brp)
{
// We're going to check interference between our
// solid and a line we're creating between the
// picked point and the user (we use the view
// direction to decide in which direction to
// draw the line)
Point3d dir =
(Point3d)Application.GetSystemVariable("VIEWDIR");
Point3d picked = per.PickedPoint,
nearerUser =
per.PickedPoint - (dir - Point3d.Origin);
// Two hits should be enough (in and out)
const int numHits = 2;
// Create out line
Line3d ln = new Line3d(picked, nearerUser);
Hit[] hits = brp.GetLineContainment(ln, numHits);
ln.Dispose();
if (hits == null || hits.Length < numHits)
return;
// Set the shortest distance to something large
// and the index to the first item in the list
double shortest = (picked - nearerUser).Length;
int found = 0;
// Loop through and check the distance to the
// user (the depth of field).
for (int idx = 0; idx < numHits; idx++)
{
Hit hit = hits;
double dist = (hit.Point - nearerUser).Length;
if (dist < shortest)
{
shortest = dist;
found = idx;
}
}
// Once we have the nearest point to the screen,
// use that one to get the containing curves
List<Curve3d> curves = new List<Curve3d>();
if (CheckContainment(
ed,
brp,
hits.Point,
ref curves
)
)
{
// If we get some back, get drawables for them and
// pass them through to the transient graphics API
TransientManager tm =
TransientManager.CurrentTransientManager;
IntegerCollection ic = new IntegerCollection();
foreach (Curve3d curve in curves)
{
Drawable d = GetDrawable(curve);
tm.AddTransient(
d,
TransientDrawingMode.DirectTopmost,
0,
ic
);
_drawn.Add(d);
}
}
}
}
tr.Commit();
}
}
private void ClearDrawnGraphics()
{
// Clear any graphics we've drawn with the transient
// graphics API, then clear the list
TransientManager tm =
TransientManager.CurrentTransientManager;
IntegerCollection ic = new IntegerCollection();
foreach (Drawable d in _drawn)
{
tm.EraseTransient(d, ic);
}
_drawn.Clear();
}
private Drawable GetDrawable(Curve3d curve)
{
// We could support multiple curve types here, but for
// now let's just return a line approximating it
Line ln = new Line(curve.StartPoint, curve.EndPoint);
ln.ColorIndex = 1;
return ln;
}
private static bool CheckContainment(
Editor ed,
Brep brp,
Point3d pt,
ref List<Curve3d> curves
)
{
bool res = false;
// Use the BRep API to get the lowest level
// container for the point
PointContainment pc;
BrepEntity be =
brp.GetPointContainment(pt, out pc);
using (be)
{
// Only if the point is on a boundary...
if (pc == PointContainment.OnBoundary)
{
// And only if the boundary is a face...
BrFace face = be as BrFace;
if (face != null)
{
// ... do we attempt to do something
try
{
foreach (BoundaryLoop bl in face.Loops)
{
// We'll return a curve for each edge in
// the containing loop
foreach (Edge edge in bl.Edges)
{
curves.Add(edge.Curve);
}
}
res = true;
}
catch (BrException)
{
res = false;
}
}
}
}
return res;
}
}
}A few comments on the implementation:
* We use the standard Editor.GetEntity() selection method - it gives us the ObjectId of the selected Solid3d but also the point that was picked.
* Using this point and the view direction, we can then draw a line (which we make as big as the diagonal of the solid's bounding box, which should be large enough) from that point in the direction of the user.
* The Boundary Representation (BRep) API allows us to determine how this line intersects the solid: we select the intersection nearest the screen, as presumably that's the one the user was intending to pick.
* We will then use the BRep API to test the solid to see whether the point is contained by (or - and this is more likely - on) the solid, and it very helpfully provides us with the lowest-level topological entity that contains the point (which we hope to be a face).
* The BRep API will throw an exception when traversing (typically during calls to GetEnumerator() for various collections) for a couple of unbounded solid-types (spheres, etc.) as we traverse: in this case we simply abort the containment checking operation.
* We use the Transient Graphics API to display the edges of the selected face. Right now we just draw lines for each curve - which will be wrong for anything with arcs or circles, for instance - but at this stage we don't care a great deal about the graphics we're drawing - this is really just to make sure we're approximately accurate, and later we'll do something more intelligent with the edges we get back for the selected face.
Here's what happens when we use the PICKFACE command to select first one face and then another of a simple box:
本帖最后由 作者 于 2009-9-17 10:36:32 编辑
六、实现一个LookAt命令
January 22, 2009
Implementing a LookAt command for AutoCAD using .NET
This post follows on from this previous one, where we looked at a technique for picking a face on an AutoCAD solid. Tony Tanzillo kindly pointed out this much cleaner solution for this problem, and also highlighted a really simple (and elegant) way to implement LookAt using standard AutoCAD commands. While I really like both pointers provided by Tony, I've decided to persevere with my existing - admittedly sub-optimal - approach, as much as to show ways to exercise some APIs that people may not have used themselves. Please be warned, this isn't the simplest way to address this problem, and it doesn't even do as much as I'd like, but anyway. :-)
Here's the C# code:
using System;
using System.Collections.Generic;
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.BoundaryRepresentation;
using BrFace =
Autodesk.AutoCAD.BoundaryRepresentation.Face;
using BrException =
Autodesk.AutoCAD.BoundaryRepresentation.Exception;
using Autodesk.AutoCAD.Interop;
namespace LookAtFace
{
public class Commands
{
public void PickFace()
{
Document doc =
Application.DocumentManager.MdiActiveDocument;
Database db = doc.Database;
Editor ed = doc.Editor;
PromptEntityOptions peo =
new PromptEntityOptions(
"\nSelect face of solid:"
);
peo.SetRejectMessage("\nMust be a 3D solid.");
peo.AddAllowedClass(typeof(Solid3d), false);
PromptEntityResult per =
ed.GetEntity(peo);
if (per.Status != PromptStatus.OK)
return;
Transaction tr =
db.TransactionManager.StartTransaction();
using (tr)
{
Solid3d sol =
tr.GetObject(per.ObjectId, OpenMode.ForRead)
as Solid3d;
if (sol != null)
{
Brep brp = new Brep(sol);
using (brp)
{
// We're going to check interference between our
// solid and a line we're creating between the
// picked point and the user (we use the view
// direction to decide in which direction to
// draw the line)
Point3d dirp =
(Point3d)Application.GetSystemVariable("VIEWDIR");
Vector3d dir = dirp - Point3d.Origin;
Point3d picked = per.PickedPoint,
nearerUser = per.PickedPoint + dir;
// Two hits should be enough (in and out)
const int numHits = 2;
// Create out line
Line3d ln = new Line3d(picked, nearerUser);
Hit[] hits = brp.GetLineContainment(ln, numHits);
ln.Dispose();
if (hits == null || hits.Length < numHits)
return;
// Set the shortest distance to something large
// and the index to the first item in the list
double shortest =
(picked - nearerUser).Length * 10;
int found = 0;
// Loop through and check the distance to the
// user (the depth of field).
for (int idx = 0; idx < numHits; idx++)
{
Hit hit = hits;
double dist = (hit.Point - nearerUser).Length;
if (dist < shortest)
{
shortest = dist;
found = idx;
}
}
Point3d closest = hits.Point;
// Once we have the nearest point to the screen,
// use that one to get the containing curves
List<Curve3d> curves = new List<Curve3d>();
if (CheckContainment(
ed,
brp,
closest,
ref curves
)
)
{
// Now we want to get a plane from our curves,
// along with its normal
Plane plane = null;
if (curves.Count == 1)
{
// If we just have one curve, hopefully it's planar
if (!curves.IsPlanar(out plane))
{
plane = null;
}
}
else if (curves.Count > 1)
{
// Otherwise we use two curves to define the plane
if (!curves.IsCoplanarWith(curves, out plane))
{
plane = null;
}
}
// Assuming we have a plane, let's check the normal
// is facing outwards
if (plane != null)
{
// Get intersections between our "normal" line
// and the solid
ln = new Line3d(closest, closest + plane.Normal);
hits = brp.GetLineContainment(ln, numHits);
// Check whether these points are actually on the
// line (if the params are zero or positive, that
// means they are on the line). If both are, then
// we need to reverse the normal, as it cuts
// through our solid
bool reverseNeeded = false;
double param1, param2;
if (ln.IsOn(hits.Point, out param1) &&
ln.IsOn(hits.Point, out param2))
{
if (
(Math.Abs(param1) <= Tolerance.Global.EqualPoint
|| param1 > 0) &&
(Math.Abs(param2) <= Tolerance.Global.EqualPoint
|| param2 > 0)
)
{
reverseNeeded = true;
}
}
ln.Dispose();
// Reverse, if needed
Vector3d norm;
if (reverseNeeded)
{
norm = -plane.Normal;
}
else
{
norm = plane.Normal;
}
// Now we set the view based on the normal
SetView(
ed,
norm,
sol.GeometricExtents
);
}
}
}
}
tr.Commit();
}
}
private static bool CheckContainment(
Editor ed,
Brep brp,
Point3d pt,
ref List<Curve3d> curves
)
{
bool res = false;
// Use the BRep API to get the lowest level
// container for the point
PointContainment pc;
BrepEntity be =
brp.GetPointContainment(pt, out pc);
using (be)
{
// Only if the point is on a boundary...
if (pc == PointContainment.OnBoundary)
{
// And only if the boundary is a face...
BrFace face = be as BrFace;
if (face != null)
{
// ... do we attempt to do something
try
{
foreach (BoundaryLoop bl in face.Loops)
{
// We'll return a curve for each edge in
// the containing loop
foreach (Edge edge in bl.Edges)
{
curves.Add(edge.Curve);
}
}
res = true;
}
catch (BrException)
{
res = false;
}
}
}
}
return res;
}
private void SetView(
Editor ed,
Vector3d viewDir,
Extents3d ext
)
{
// We do a two part zoom... one gets us in the right
// viewing direction
ViewTableRecord rec = new ViewTableRecord();
rec.IsPaperspaceView = false;
rec.ViewDirection = viewDir;
rec.CenterPoint = Point2d.Origin;
rec.ViewTwist = 0.0;
ed.SetCurrentView(rec);
// And the other does a Zoom Window
ZoomWin(
ed,
new Point2d(ext.MinPoint.X, ext.MinPoint.Y),
new Point2d(ext.MaxPoint.X, ext.MaxPoint.Y)
);
}
private static void ZoomWin(
Editor ed, Point2d min, Point2d max
)
{
string lower =
min.ToString().Substring(
1,
min.ToString().Length - 2
);
string upper =
max.ToString().Substring(
1,
max.ToString().Length - 2
);
string cmd =
"_.ZOOM _W " + lower + " " + upper + " ";
// Send the command(s)
SendQuietCommand(ed.Document, cmd);
}
#region QuietCommandCalling
const string kFinishCmd = "FINISH_COMMAND";
private static void SendQuietCommand(
Document doc,
string cmd
)
{
// Get the old value of NOMUTT
object nomutt =
Application.GetSystemVariable("NOMUTT");
// Add the string to reset NOMUTT afterwards
AcadDocument oDoc =
(AcadDocument)doc.AcadDocument;
oDoc.StartUndoMark();
cmd += "_" + kFinishCmd + " ";
// Set NOMUTT to 1, reducing cmd-line noise
Application.SetSystemVariable("NOMUTT", 1);
doc.SendStringToExecute(cmd, true, false, false);
}
static public void FinishCommand()
{
Document doc =
Application.DocumentManager.MdiActiveDocument;
AcadDocument oDoc =
(AcadDocument)doc.AcadDocument;
oDoc.EndUndoMark();
Application.SetSystemVariable("NOMUTT", 0);
}
#endregion
}
}
Some comments on the implementation:
* I've split the view change into two sections:
o Change the view using ed.SetCurrentView() to have the right view direction
o Use quiet command calling to Zoom Window on the face's extents
* My ideal would be to have a smooth transition between the 3D views, showing the rotation of the model
o I'm looking into ways to do this - SetCurrentView() doesn't support smooth view transitions, but I'm hoping that since the introduction of the ViewCube we have some other API of which I'm unaware that will fit the bill
Anyway, that's it for today. Please bear in mind the various caveats I've made about different approaches to solving this problem: while I don't usually like to post something using a sub-optimal technique, I think there are still pieces of the code that people will find of value.
七、创建一个可编辑的三维实体
February 20, 2009
Creating an editable AutoCAD solid using .NET
This question came up recently on the AutoCAD .NET Discussion Group: how to create a Solid3d object which provides the user with the full set of grips to manipulate it (which I've abbreviated to "editable" in the title of this post :-). This comes down to a enhancements that were made in AutoCAD 2007 to allow better manipulation of solids via the user-interface via extended grips and a push-pull mechanism. These capabilities need to be enabled solids as you create them - and unfortunately cannot be retro-fitted to existing solid objects - by telling the solid that you would like it to record its history. And it's really that simple, you simply have to set the RecordHistory flag to true.
The below C# code demonstrates how this works, by exposing a command that prompts the user whether to set this flag to true, or not:
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.Geometry;
namespace SolidCreation
{
public class Commands
{
public void CylinderWithHistory()
{
Document doc =
Application.DocumentManager.MdiActiveDocument;
Database db = doc.Database;
Editor ed = doc.Editor;
// Ask the user whether to create history
// and where to place the cylinder
bool createHistory = false;
PromptKeywordOptions pko =
new PromptKeywordOptions(
"\nRecord history for this cylinder?"
);
pko.AllowNone = true;
pko.Keywords.Add("Yes");
pko.Keywords.Add("No");
pko.Keywords.Default = "Yes";
PromptResult pkr =
ed.GetKeywords(pko);
if (pkr.Status != PromptStatus.OK)
return;
if (pkr.StringResult == "Yes")
createHistory = true;
PromptPointResult ppr =
ed.GetPoint("\nSelect point: ");
if (ppr.Status != PromptStatus.OK)
return;
Point3d pt = ppr.Value;
Transaction tr =
db.TransactionManager.StartTransaction();
using (tr)
{
// Create the solid and set the history flag
Solid3d sol = new Solid3d();
sol.RecordHistory = createHistory;
// Hardcode the dimensions of the cylinder
// for the purpose of this example
sol.CreateFrustum(10, 3, 3, 3);
// Add the Solid3d to the modelspace
BlockTable bt =
(BlockTable)tr.GetObject(
db.BlockTableId,
OpenMode.ForRead
);
BlockTableRecord ms =
(BlockTableRecord)tr.GetObject(
bt,
OpenMode.ForWrite
);
ms.AppendEntity(sol);
tr.AddNewlyCreatedDBObject(sol, true);
// And transform it to the selected point
sol.TransformBy(
Matrix3d.Displacement(pt - Point3d.Origin)
);
tr.Commit();
}
}
}
}
Let's see the results of the CWH command - in this case we run it twice, selecting "No" to the question about history creation for the cylinder on the left and "Yes" to the same question for the cylinder on the right:
As pointed out in the thread on the discussion group, there are some limitations to the API around the history recording for solids: you cannot, for instance, programmatically manipulate the solid's history: it's only possible to create new solids that will then allow users to make use of editing operations that effect the history.
On a somewhat related note, back in this post I mentioned a hole in the .NET API around creating a swept solid. I've been informed by a friend in our Engineering team that we've now plugged it - and some other related holes - in the .NET API for AutoCAD 2010. You will now have the following methods available from the Solid3d class: CreateExtrudedSolid, CreateLoftedSolid, CreateRevolvedSolid and CreateSweptSolid (the equivalent .NET methods for Surfaces existed already, as well as the base C++ methods in ObjectARX). Thanks for the heads-up, Joel! :-)
本帖最后由 作者 于 2009-9-17 11:11:52 编辑
八、不规则建模
March 25, 2009
Free-form modeling in AutoCAD 2010 using .NET
A big thanks to Stephen Preston, who manages DevTech Americas and coordinates our worldwide AutoCAD workgroup as well as spending time working with the AutoCAD Engineering team (phew!), for providing this sample. Stephen originally put it together for our annual Developer Days tour late last year: I took the original sample, converted it from VB.NET to C# and made some minor changes to the code. The VB.NET version is available from the ADN website, in case.
The Free-Form Design feature in AutoCAD 2010 is one of the coolest enhancements to the product (I really like the Parametric Drawing feature, too, although as the API for that is currently C++-only it’s unfortunately going to get a little less air-time on this blog). This post looks at how to automate free-form design operations: starting with a traditional Solid3d object, converting it to a sub-division mesh (SubDMesh) which we then manipulate in a number of interesting ways before converting back to a Solid3d.
Here’s the C# code:
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.Colors;
using System.Collections.Generic;
using System;
namespace FreeformModeling
{
public class Commands
{
// Add a new mesh to the current space,
// setting it to a sphere primitive -
// no smoothing.
public void SphericalMesh()
{
Document doc =
Application.DocumentManager.MdiActiveDocument;
Database db = doc.Database;
Transaction tr =
doc.TransactionManager.StartTransaction();
using (tr)
{
BlockTableRecord btr =
(BlockTableRecord)tr.GetObject(
db.CurrentSpaceId,
OpenMode.ForWrite
);
SubDMesh mySubD = new SubDMesh();
mySubD.SetSphere(20, 8, 8, 0);
mySubD.SetDatabaseDefaults();
btr.AppendEntity(mySubD);
tr.AddNewlyCreatedDBObject(mySubD, true);
tr.Commit();
}
}
// Make clone of solid, inflate it by a fraction of its
// extents, create a mesh from the clone, erase the clone.
private ObjectId cloneAndInflateSolid(
Transaction tr, ObjectId solidId
)
{
// Now copy the solid
Document doc =
Application.DocumentManager.MdiActiveDocument;
Database db = doc.Database;
Editor ed = doc.Editor;
ObjectId newId;
// Inflate solid by user specified fraction
// (0.05 works well with sample drawing)
PromptDoubleOptions pdo =
new PromptDoubleOptions(
"\nSpecify inflation as fraction of bounds:"
);
PromptDoubleResult pdr =
ed.GetDouble(pdo);
// Return if something went wrong.
if (pdr.Status != PromptStatus.OK)
return ObjectId.Null;
double frac = pdr.Value;
ObjectIdCollection ids = new ObjectIdCollection();
ids.Add(solidId);
IdMapping im = new IdMapping();
db.DeepCloneObjects(ids, db.CurrentSpaceId, im, false);
newId = im.Lookup(solidId).Value;
// We now have the ObjectID of the newly cloned
// solid in newId
// Inflate by length of diagonal vector across bounds
// multiplied by inflation fraction
Solid3d sol =
(Solid3d)tr.GetObject(newId, OpenMode.ForWrite);
Extents3d ext = (Extents3d)sol.Bounds;
Vector3d vec = ext.MaxPoint - ext.MinPoint;
sol.OffsetBody(vec.Length * frac);
// Define params governing mesh generation algorithm
// (See ObjectARX helpfiles for explanation of params)
MeshFaceterData fd =
new MeshFaceterData(
0.01 * vec.Length,
40 * Math.PI / 180,
2, 2, 15, 5, 5, 0
);
// Get the mesh data from the solid
// (with smoothing level 1).
MeshDataCollection mdc =
SubDMesh.GetObjectMesh(sol, fd);
// Create the mesh defined by this data.
SubDMesh sd = new SubDMesh();
sd.SetDatabaseDefaults();
sd.SetSubDMesh(mdc.VertexArray, mdc.FaceArray, 1);
// Add the mesh to database.
BlockTableRecord btr =
(BlockTableRecord)tr.GetObject(
db.CurrentSpaceId,
OpenMode.ForWrite
);
newId = btr.AppendEntity(sd);
tr.AddNewlyCreatedDBObject(sd, true);
// Erase the cloned solid
sol.Erase();
return newId;
}
// Add creases to user selected faces
// Returns true if successful
private bool addCreases(
Transaction tr, ObjectId meshId
)
{
Document doc =
Application.DocumentManager.MdiActiveDocument;
PromptSelectionOptions pso =
new PromptSelectionOptions();
pso.ForceSubSelections = true;
pso.AllowDuplicates = false;
pso.MessageForAdding = "\nSelect edges to crease: ";
PromptSelectionResult selRes =
doc.Editor.GetSelection(pso);
// If the user didn't make valid selection, we return
if (selRes.Status != PromptStatus.OK)
return false;
SelectionSet ss = selRes.Value;
// They may have picked more than one object - we need
// the subobjects of the object with the right ObjectId
SelectedObject so = null;
foreach (SelectedObject o in ss)
{
if (o.ObjectId == meshId)
so = o;
}
// If they picked on the wrong object, then just don't
// add any extrusions
if (so == null)
return false;
// We got to here so selection was valid
// This will store selected edges
List<FullSubentityPath> creasePaths =
new List<FullSubentityPath>();
DBObject obj =
tr.GetObject(so.ObjectId, OpenMode.ForRead);
SubDMesh sd = obj as SubDMesh;
if (sd != null)
{
sd.UpgradeOpen();
// Add all selected subentities to the collection
SelectedSubObject[] subEnts =
so.GetSubentities();
foreach (SelectedSubObject subEnt in subEnts)
{
if (subEnt.FullSubentityPath.SubentId.Type
== SubentityType.Edge)
creasePaths.Add(subEnt.FullSubentityPath);
}
// Add infinite creases to all those edges
// -1 means 'always' crease
sd.SetCrease(creasePaths.ToArray(), -1);
return true;
}
return false;
}
// Extrude each mesh face by a random amount, up to a
// user specified maximum. Returns true if successful.
private bool extrudeFaces(
Transaction tr, ObjectId meshId
)
{
Document doc =
Application.DocumentManager.MdiActiveDocument;
// Now we want to stretch a few faces to add a bit of
// randomness. Better just stretch outwards.
// Prompt for max random bump (0.02 works well for
// sample drawing).
PromptDoubleOptions pdo =
new PromptDoubleOptions(
"\nSpecify bump size as fraction of bounds: "
);
PromptDoubleResult pdr =
doc.Editor.GetDouble(pdo);
// If user cancels value entry, then we return.
if (pdr.Status != PromptStatus.OK)
return false;
double extrudelen = pdr.Value;
PromptSelectionOptions pso =
new PromptSelectionOptions();
pso.ForceSubSelections = true;
pso.MessageForAdding = "\nSelect faces to bump: ";
PromptSelectionResult psr =
doc.Editor.GetSelection(pso);
// If user cancels selection, then we assume they want
// to continue, but that they don't want to extrude any
// surfaces
if (psr.Status != PromptStatus.OK)
return false;
SelectionSet ss = psr.Value;
// They may have picked more than one object - we need
// the subobjects of the object with the right ObjectId
SelectedObject so = null;
foreach (SelectedObject o in ss)
{
if (o.ObjectId == meshId)
so = o;
}
if (so == null)
return false;
// If they picked on the wrong object, then just don't
// add any extrusions
// We got to here so selection was valid
// Stores faces we selected for random extrusion
List<FullSubentityPath> bumpPaths =
new List<FullSubentityPath>();
DBObject obj =
tr.GetObject(so.ObjectId, OpenMode.ForRead);
SubDMesh sd = obj as SubDMesh;
if (sd != null)
{
sd.UpgradeOpen();
SelectedSubObject[] subEnts =
so.GetSubentities();
// We'll be extruding faces by a random amount, so
// instantiate random number generator here ready
// to use inside loop
Random rnd = new Random();
// Process each selected face in turn
foreach (SelectedSubObject subEnt in subEnts)
{
// Is it a face?
if (subEnt.FullSubentityPath.SubentId.Type
== SubentityType.Face)
{
FullSubentityPath[] faces =
new FullSubentityPath;
faces = subEnt.FullSubentityPath;
// Find normal for this face
Plane fPlane =
sd.GetFacePlane(faces.SubentId);
Vector3d norm = fPlane.Normal;
// Get length of diagonal across bounds to use
// in calculating bump scale
Extents3d ext = (Extents3d)sd.Bounds;
Vector3d vec = ext.MaxPoint - ext.MinPoint;
Matrix3d mat =
Matrix3d.Displacement(
norm * 0.5 * extrudelen *
rnd.NextDouble() * vec.Length
);
sd.TransformSubentityPathsBy(faces, mat);
}
}
return true;
}
return false;
}
// Set random color for each mesh face.
private void colorFaces(
Transaction tr, ObjectId meshId
)
{
Random rnd = new Random();
DBObject obj =
tr.GetObject(meshId, OpenMode.ForRead);
SubDMesh sd = obj as SubDMesh;
if (sd != null)
{
sd.UpgradeOpen();
// Iterate all faces and set to a random color
for (int i = 0; i <= sd.NumberOfFaces - 1; i++)
{
SubentityId sId =
new SubentityId(SubentityType.Face, i);
byte[] rgb = new byte;
rnd.NextBytes(rgb);
Color col =
Color.FromRgb(rgb, rgb, rgb);
sd.SetSubentColor(sId, col);
}
}
}
private ObjectId convertToSolid(
Transaction tr, ObjectId meshId
)
{
Document doc =
Application.DocumentManager.MdiActiveDocument;
Database db = doc.Database;
Editor ed = doc.Editor;
ObjectId newId = ObjectId.Null;
DBObject obj =
tr.GetObject(meshId, OpenMode.ForRead);
SubDMesh sd = obj as SubDMesh;
if (sd != null)
{
sd.UpgradeOpen();
// ConvertToSolid will throw an exception if
// it can't create a solid from the mesh.
Solid3d sol = null;
try
{
sol = sd.ConvertToSolid(true, true);
}
catch
{
ed.WriteMessage(
"\nMesh was too complex to turn into a solid."
);
}
BlockTableRecord btr =
(BlockTableRecord)tr.GetObject(
db.CurrentSpaceId,
OpenMode.ForWrite
);
newId = btr.AppendEntity(sol);
tr.AddNewlyCreatedDBObject(sol, true);
}
return newId;
}
// Translates entity along x-axis by factor multiplied by
// the entity's extent along the x-axis.
private void translateEntity(
Transaction tr, ObjectId objId, double factor
)
{
Entity ent =
(Entity)tr.GetObject(
objId,
OpenMode.ForWrite
);
Extents3d ext = (Extents3d)ent.Bounds;
Point3d pt1 =
new Point3d(ext.MaxPoint.X, ext.MinPoint.Y, ext.MinPoint.Z);
Vector3d transVec =
factor * (pt1 - ext.MinPoint);
Matrix3d mat = Matrix3d.Displacement(transVec);
ent.TransformBy(mat);
}
// Creates a SubDMesh based on an 'inflated' clone of the
// selected solid. The user then gets to add creases and
// select which faces will be given a random extrusion.
// It colors each face of the mesh and then converts it to
// a smooth solid.
public void CreateCasing()
{
Document doc =
Application.DocumentManager.MdiActiveDocument;
Editor ed = doc.Editor;
ObjectId meshId;
// First select the solid (just one)
PromptEntityOptions peo =
new PromptEntityOptions("\nSelect a solid: ");
peo.SetRejectMessage("\nObject must be a 3D solid.");
peo.AddAllowedClass(typeof(Solid3d), false);
PromptEntityResult per = ed.GetEntity(peo);
if (per.Status != PromptStatus.OK)
return;
Transaction tr =
doc.TransactionManager.StartTransaction();
using (tr)
{
meshId =
cloneAndInflateSolid(tr, per.ObjectId);
// If the above call returned a null ObjectId,
// then something went wrong - we need to stop now
if (meshId == ObjectId.Null)
return;
// Now we have our original solid and the mesh
// created from the cloned solid
// Add creases
if (!addCreases(tr, meshId))
return;
// Randomly extrude faces
if (!extrudeFaces(tr, meshId))
return;
// Give faces random colors
colorFaces(tr, meshId);
// Convert the mesh to a solid.
ObjectId solId =
convertToSolid(tr, meshId);
// Move the entities so they don't sit on
// top of one other
translateEntity(tr, meshId, 1.5);
translateEntity(tr, solId, 3);
doc.TransactionManager.QueueForGraphicsFlush();
tr.Commit();
}
}
}
}Here’s a sample 3D model – consisting of a single, composite 3D solid:
Here’s what happens when we run the CC command, selecting this solid and applying the recommended values (0.2 – i.e. 20% – for the inflation percentage and 0.05 – i.e. 5% – for the maximum random bump). I selected a number of edges and faces at random after which the code assigned random colours to all the faces, so please don’t get frustrated trying to reproduce these exact results. :-)
The objects are (going from right to left):
1. The original solid.
2. A sub-division mesh created by inflating the original solid and applying creases to certain edges and extruding certain faces.
3. A traditional AutoCAD solid created from the sub-division mesh.
The best way to understand the overall functionality of the application is to scan the CC command – defined by the CreateCasing function found at the bottom of the listing – and then looking into individual functions it uses. The application also defines another command – SPHM – which shows how to create a simple, spherical sub-division mesh.
页:
[1]