Revit API - Can't paint generated Direct Shapes - c#

I generate all of them the same, but some of them can't be colored (image on the bottom)
Steps:
I'm creating list of faces from solid
internal static List<List<XYZ>> GetFacesFromSolidTriangulate(Solid geomSolid)
{
List<List<XYZ>> faces = new List<List<XYZ>>();
foreach (Face face in geomSolid.Faces)
{
Mesh mesh_space = face.Triangulate();
for (int i = 0; i < mesh_space.NumTriangles; i++)
{
MeshTriangle triangle = mesh_space.get_Triangle(i);
XYZ p1 = triangle.get_Vertex(0);
XYZ p2 = triangle.get_Vertex(1);
XYZ p3 = triangle.get_Vertex(2);
List<XYZ> xyz = new List<XYZ>();
xyz.Add(triangle.get_Vertex(0));
xyz.Add(triangle.get_Vertex(1));
xyz.Add(triangle.get_Vertex(2));
faces.Add(xyz);
}
}
return faces;
}
I'm creating Direct Shape using
static public DirectShape NewDrawDirectShape(Document doc, List<List<XYZ>> faces, ElementId matId, string name)
{
TessellatedShapeBuilder builder = new TessellatedShapeBuilder();
builder.OpenConnectedFaceSet(true);
foreach(List<XYZ> face in faces)
{
builder.AddFace(new TessellatedFace(face, matId));
}
builder.CloseConnectedFaceSet();
builder.Build();
TessellatedShapeBuilderResult result = builder.GetBuildResult();
DirectShape ds = DirectShape.CreateElement(doc, new ElementId(BuiltInCategory.OST_GenericModel));
ds.SetShape(result.GetGeometricalObjects());
ds.Name = name;
return ds;
}
And here is the problem, I can't paint few of them even using Revit "paint" tool...
Red arrow define what I'm trying achieve on this direct shape, blue direct shapes works correctly

Have you checked the triangle orientations? Maybe some of the triangles returned in the MeshTriangle objects are incorrectly oriented. To try it out, you could create a little model line in the centre of each triangle indicating its normal vector and this the edge orientation.

Related

How to get XYZ data of room center?

I am trying to place an element into the center of several rooms. So far I have achieved something similar by using the Location Point which has placed the element close to the center but not exact.
I attempted to fix this by using two methods that I believed would help accomplish this task, GetElementCenter and GetRoomCenter but when I run the plugin, nothing happens.
class Class2
{
public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)
{
//Get access to Revit command data, user interface and document
UIApplication uiapp = commandData.Application;
UIDocument uidoc = uiapp.ActiveUIDocument;
Document doc = uidoc.Document;
//Collect all rooms
FilteredElementCollector roomCollector = new FilteredElementCollector(doc).OfClass(typeof(SpatialElement));
// Collect element
FilteredElementCollector element = new FilteredElementCollector(doc).OfClass(typeof(FamilySymbol)).OfCategory(BuiltInCategory.OST_Cameras);
//Get symbol
FamilySymbol elementSym = element.FirstElement() as FamilySymbol;
using (Transaction tx = new Transaction(doc))
{
try
{
tx.Start("Start");
//For loop for every room in the roomCollector
foreach (SpatialElement oneRoom in roomCollector)
{
//Get area of each room
Room room = oneRoom as Room;
double area = room.Area;
//Location point version
Location loc = room.Location;
LocationPoint location = loc as LocationPoint;
XYZ point = (null == location) ? XYZ.Zero : location.Point;
//New version
XYZ source = GetRoomCenter(room);
double smallRoom = 301;
if (area <= smallRoom)
{
doc.Create.NewFamilyInstance(source, elementSym, Autodesk.Revit.DB.Structure.StructuralType.NonStructural);
}
}
tx.Commit();
}
catch (Exception e)
{
Debug.Print(e.Message);
tx.RollBack();
}
}
TaskDialog.Show("Message", "Task completed successfully");
return Result.Succeeded;
}
public XYZ GetElementCenter(Room room)
{
BoundingBoxXYZ bounding = room.get_BoundingBox(null);
XYZ center = (bounding.Max + bounding.Min) * 0.5;
return center;
}
public XYZ GetRoomCenter(Room room)
{
XYZ boundCenter = GetElementCenter(room);
LocationPoint locPt = (LocationPoint)room.Location;
XYZ roomCenter = new XYZ(boundCenter.X, boundCenter.Y, locPt.Point.Z);
return roomCenter;
}
}
}
Any help on getting the XYZ data of the center of a room would be greatly appreciated.
Assuming that you have straight walls bounding the room, I would suggest that you take a look at mathematical and geometrical algorithms for determining the center point of a polygon. Something like this new algorithm for finding a visual center of a polygon is probably best suited to your needs, even though you neither state this fact nor probably are yet aware of it :-)

How to get the combined bounds of multiple UI image in a canvas?

I'm trying to get the bounds of multiple ui images inside a canvas. I'm using this code:
Bounds bounds = new Bounds(imageList[0].transform.position, Vector3.zero);
for (int i = 0; i < imageList.Count; i++)
{
bounds.Encapsulate(imageList[i].transform.position);
}
But if I have two images, the bound will start and ends in the middle of each image. This code is working when using a gameobject cube, sphere etc. but different result when using UI.
You can use RectTransform.GetWorldCorners in order to get the 4 corners of each Image in word coordinates.
Then you can iterate over them and use Vector3.Min and Vector3.Max to calculate the minimum and maximum of all corners of all images.
And finally use Bounds.SetMinMax in order to create a bounding box using this minimum and maximum.
public class Example : MonoBehaviour
{
public List<Image> imageList = new List<Image>();
private void OnDrawGizmos()
{
var min = Vector3.positiveInfinity;
var max = Vector3.negativeInfinity;
foreach (var image in imageList)
{
if(!image) continue;
// Get the 4 corners in world coordinates
var v = new Vector3[4];
image.rectTransform.GetWorldCorners(v);
// update min and max
foreach (var vector3 in v)
{
min = Vector3.Min(min, vector3);
max = Vector3.Max(max, vector3);
}
}
// create the bounds
var bounds = new Bounds();
bounds.SetMinMax(min, max);
Gizmos.color = Color.red;
Gizmos.DrawWireCube(bounds.center, bounds.size);
}
}
Note: This bounding box will be world aligned with the global XYZ axis (as was your original attempt).
Use RectTransform.GetWorldCorners to get all corners of the images.
Find the minimal and maximal points.
Use Bounds.SetMinMax to get the bounds.

Setting x,y position of Visio shape to a graph from a template file C#

I have a template file (.vsdx) which contains a graph with a fixed x and y axis that I load into a new Visio document. I've managed to insert a shape onto the Visio document but it doesn't position according to the the x and y axis of the graph.
Example: Setting the vshape with co-ords 0,0 positions to the bottom left corner edge of the document.
I have the following code so far:
//decalre and initialize Visio objects
var vApp = new Visio.Application();
Visio.Document vDoc, vStencil;
Visio.Page vPage;
Visio.Shape vToShape, vFromShape, vConnector;
Visio.Master vConnectorMaster, vFlowChartMaster;
double dblXLocation;
double dblYLocation;
Visio.Cell vBeginCell, vEndCell;
int iCount;
string TEMPLATEPATH = #"C:\temp\TestProject\testTemplate.vsdx";
//Change this constant to match your choice of location and file name.
string SAVENEWFILE = #"C:\temp\TestProject\testFile.vsdx";
vFlowChartMaster = vStencil.Masters[aryValues[0, 0]];
dblXLocation = 1;
dblYLocation = 1;
vToShape = vPage.Drop(vFlowChartMaster,
dblXLocation, dblYLocation);
vToShape.Text = "Test";
vDoc.Pages[1].Name = "Flowchart Example";
try
{
//Delete the previous version of the file.
//Kill(SAVENEWFILE);
File.Delete(SAVENEWFILE);
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
vDoc.SaveAs(SAVENEWFILE);
vDoc.Close();
vApp.Quit();
vDoc = null;
vApp = null;
GC.Collect();
The graph that gets loaded onto the Visio doc is here
Ok, thanks for the update comment. In that case, here's a quick sample. I've created a drawing with a basic 'Graph' master shape, which defines an origin, and a 'Dot' master which is simply a small circle to drop as a dta marker.
The code (using LINQPad) looks for the first instance of the Graph master and then looks for 'known' cells (which it's up to you to define) to get hold of the origin. It then drops two 'Dot' shapes relative to the Graph origin.
Here's what the Graph shape looks like:
[Note - that you can reference a PNT type in an X or Y cell and Visio will extract the corresponding X or Y coordinate]
void Main()
{
var vApp = MyExtensions.GetRunningVisio();
var vPag = vApp.ActivePage;
var graphShp = vPag.Shapes.Cast<Visio.Shape>()
.FirstOrDefault(s => s.Master?.Name == "Graph");
if (graphShp != null)
{
var dotMst = vPag.Document.Masters["Dot"];
//Get x / y back as a named tuple
var origin = GetGraphOrigin(graphShp);
//Green fill is the default defined in the master
var greenDotShp = vPag.Drop(dotMst, origin.x, origin.y);
//Use offest based on graph origin
var redDotOffsetX = -0.5;
var redDotOffsetY = 0.25;
var redDotShp = vPag.Drop(dotMst, origin.x + redDotOffsetX, origin.y + redDotOffsetY);
redDotShp.CellsU["FillForegnd"].FormulaU = "RGB(200,40,40)";
}
}
private (double x, double y) GetGraphOrigin(Visio.Shape targetShp)
{
const string originX = "User.OriginOnPageX";
const string originY = "User.OriginOnPageY";
if (targetShp == null)
{
throw new ArgumentNullException();
}
if (targetShp.CellExistsU[originX, (short)Visio.VisExistsFlags.visExistsAnywhere] != 0
&& targetShp.CellExistsU[originY, (short)Visio.VisExistsFlags.visExistsAnywhere] != 0)
{
return (x: targetShp.CellsU[originX].ResultIU,
y: targetShp.CellsU[originY].ResultIU);
}
return default;
}
So if you run this code, you should end up with something like this (assuming you started off with the a drawing as described above):
So there are lots of ways you could approach this, but probably you need some method or reading where in your Graph shape the origin is and then use that in positioning your 'dot' shapes.

Partial Convex Hull

I have a polygon with vertices P1, P2,P3,.....P11. My vertex coordinate data types are double.
I also have a line between P1, P7.
I want to create a partial convex hull between P1 and P7 and keep my original polygon vertices after P7.
So final polygon will be as follow;
So far I convert the whole polygon to convex hull, delete vertices in convex hull and add hull vertices. It works fine with small polygons but it won't be easy to manage that way when vertex number increases.
I tried to look for c# algorithms available for partial convex hull but i couldn't find anything except some researches.
Any ideas?
Remove vertices P8 through P11 from your polygon and store them off, then run the convex hull conversion on what's left (P1 through P7). After, re-insert vertices P8 through P11 (between P7 and P1).
I clipped the polygon by using DotSpatial
internal static IGeometry Polygonize(IGeometry geometry)
{
var lines = LineStringExtracter.GetLines(geometry);
var polygonizer = new Polygonizer();
polygonizer.Add(lines);
var polys = polygonizer.GetPolygons();
var polyArray = GeometryFactory.ToGeometryArray(polys);
return geometry.Factory.CreateGeometryCollection(polyArray);
}
internal static IGeometry PolygonizeForClip(IGeometry geometry, IPreparedGeometry clip)
{
var lines = LineStringExtracter.GetLines(geometry);
var clippedLines = new List<IGeometry>();
foreach (ILineString line in lines)
{
if (clip.Contains(line))
clippedLines.Add(line);
}
var polygonizer = new Polygonizer();
polygonizer.Add(clippedLines);
var polys = polygonizer.GetPolygons();
var polyArray = GeometryFactory.ToGeometryArray(polys);
return geometry.Factory.CreateGeometryCollection(polyArray);
}
internal static IGeometry SplitPolygon(IGeometry polygon, IGeometry line)
{
var nodedLinework = polygon.Boundary.Union(line);
var polygons = Polygonize(nodedLinework);
// only keep polygons which are inside the input
var output = new List<IGeometry>();
for (var i = 0; i < polygons.NumGeometries; i++)
{
var candpoly = (IPolygon)polygons.GetGeometryN(i);
if (polygon.Contains(candpoly.InteriorPoint))
output.Add(candpoly);
}
return polygon.Factory.BuildGeometry(output);
}
internal static IGeometry ClipPolygon(IGeometry polygon, IPolygonal clipPolygonal)
{
var clipPolygon = (IGeometry)clipPolygonal;
var nodedLinework = polygon.Boundary.Union(clipPolygon.Boundary);
var polygons = Polygonize(nodedLinework);
// only keep polygons which are inside the input
var output = new List<IGeometry>();
for (var i = 0; i < polygons.NumGeometries; i++)
{
var candpoly = (IPolygon)polygons.GetGeometryN(i);
var interiorPoint = candpoly.InteriorPoint;
if (polygon.Contains(interiorPoint) &&
clipPolygon.Contains(interiorPoint))
output.Add(candpoly);
}
return polygon.Factory.BuildGeometry(output);
}
then
var Splitted = SplitPolygon(Polygon, Line);
Then added parts.

Update color of single point of GeometryModel3D Material rather than whole system of points

I'm having trouble getting my head around the colour/material system of C# WPF projects, currently I am updating the colour of an entire system of points on each update of the model when I would instead like to just update the colour of a single point (as it is added).
AggregateSystem Class
public class AggregateSystem {
// stack to store each particle in aggregate
private readonly Stack<AggregateParticle> particle_stack;
private readonly GeometryModel3D particle_model;
// positions, indices and texture co-ordinates for particles
private readonly Point3DCollection particle_positions;
private readonly Int32Collection triangle_indices;
private readonly PointCollection text_coords;
// brush to apply to particle_model.Material
private RadialGradientBrush rad_brush;
// ellipse for rendering
private Ellipse ellipse;
private RenderTargetBitmap render_bitmap;
public AggregateSystem() {
particle_stack = new Stack<AggregateParticle>();
particle_model = new GeometryModel3D { Geometry = new MeshGeometry3D() };
ellipse = new Ellipse {
Width = 32.0,
Height = 32.0
};
rad_brush = new RadialGradientBrush();
// fill ellipse interior using rad_brush
ellipse.Fill = rad_brush;
ellipse.Measure(new Size(32,32));
ellipse.Arrange(new Rect(0,0,32,32));
render_bitmap = new RenderTargetBitmap(32,32,96,96,PixelFormats.Pbgra32));
ImageBrush img_brush = new ImageBrush(render_bitmap);
DiffuseMaterial diff_mat = new DiffuseMaterial(img_brush);
particle_model.Material = diff_mat;
particle_positions = new Point3DCollection();
triangle_indices = new Int32Collection();
tex_coords = new PointCollection();
}
public Model3D AggregateModel => particle_model;
public void Update() {
// get the most recently added particle
AggregateParticle p = particle_stack.Peek();
// compute position index for triangle index generation
int position_index = particle_stack.Count * 4;
// create points associated with particle for circle generation
Point3D p1 = new Point3D(p.position.X, p.position.Y, p.position.Z);
Point3D p2 = new Point3D(p.position.X, p.position.Y + p.size, p.position.Z);
Point3D p3 = new Point3D(p.position.X + p.size, p.position.Y + p.size, p.position.Z);
Point3D p4 = new Point3D(p.position.X + p.size, p.position.Y, p.position.Z);
// add points to particle positions collection
particle_positions.Add(p1);
particle_positions.Add(p2);
particle_positions.Add(p3);
particle_positions.Add(p4);
// create points for texture co-ords
Point t1 = new Point(0.0, 0.0);
Point t2 = new Point(0.0, 1.0);
Point t3 = new Point(1.0, 1.0);
Point t4 = new Point(1.0, 0.0);
// add texture co-ords points to texcoords collection
tex_coords.Add(t1);
tex_coords.Add(t2);
tex_coords.Add(t3);
tex_coords.Add(t4);
// add position indices to indices collection
triangle_indices.Add(position_index);
triangle_indices.Add(position_index + 2);
triangle_indices.Add(position_index + 1);
triangle_indices.Add(position_index);
triangle_indices.Add(position_index + 3);
triangle_indices.Add(position_index + 2);
// update colour of points - **NOTE: UPDATES ENTIRE POINT SYSTEM**
// -> want to just apply colour to single particles added
rad_brush.GradientStops.Add(new GradientStop(p.colour, 0.0));
render_bitmap.Render(ellipse);
// set particle_model Geometry model properties
((MeshGeometry3D)particle_model.Geometry).Positions = particle_positions;
((MeshGeometry3D)particle_model.Geometry).TriangleIndices = triangle_indices;
((MeshGeometry3D)particle_model.Geometry).TextureCoordinates = tex_coords;
}
public void SpawnParticle(Point3D _pos, Color _col, double _size) {
AggregateParticle agg_particle = new AggregateParticle {
position = _pos, colour = _col, size = _size;
}
// push most-recently-added particle to stack
particle_stack.Push(agg_particle);
}
}
where AggregateParticle is a POD class consisting of Point3D position, Color color and double size fields which are self-explanatory.
Is there any simple and efficient method to update the colour of the single particle as it is added in the Update method rather than the entire system of particles? Or will I need to create a List (or similar data structure) of DiffuseMaterial instances for each and every particle in the system and apply brushes for the necessary colour to each?
[The latter is something I want to avoid at all costs, partly due to the fact it would require large structural changes to my code, and I am certain that there is a better way to approach this than that - i.e. there MUST be some simple way to apply colour to a set of texture co-ordinates, surely?!.]
Further Details
AggregateModel is a single Model3D instance corresponding to the field particle_model which is added to a Model3DGroup of the MainWindow.
I should note that what I am trying to achieve, specifically, here is a "gradient" of colours for each particle in an aggregate structure where a particle has a Color in a "temperature-gradient" (computed elsewhere in the program) which is dependent upon order in which it was generated - i.e. particles have a colder colour if generated earlier and a warmer colour if generated later. This colour list is pre-computed and passed to each particle in the Update method as can be seen above.
One solution I attempted involved creating a separate AggregateComponent instance for each particle where each of these objects has an associated Model3D and thus a corresponding brush. Then an AggregateComponentManager class was created which contained the List of each AggregateComponent. This solution works, however it is horrendously slow as each component has to be updated every time a particle is added so memory usage explodes - is there a way to adapt this where I can cache already rendered AggregateComponents without having to call their Update method each time a particle is added?
Full source code (C# code in the DLAProject directory) can be found on GitHub: https://github.com/SJR276/DLAProject
We create WPF 3D models for smallish point clouds (+/- 100 k points) where each point is added as an octahedron (8 triangles) to a MeshGeometry3D.
To allow different colors for different points (we use this for selecting one or a subset of points) in such a point cloud, we assign texture coordinates from a small Bitmap.
At a high level we have some code like this:
BitmapSource bm = GetColorsBitmap(new List<Color> { BaseColor, SelectedColor });
ImageBrush ib = new ImageBrush(bm)
{
ViewportUnits = BrushMappingMode.Absolute,
Viewport = new Rect(0, 0, 1, 1) // Matches the pixels in the bitmap.
};
GeometryModel3D model = new GeometryModel3D { Material = new DiffuseMaterial(ib) };
and now the texture coordinates are just
new Point(0, 0);
new Point(1, 0);
... etc.
The colors Bitmap comes from:
// Creates a bitmap that has a single row containing single pixels with the given colors.
// At most 256 colors.
public static BitmapSource GetColorsBitmap(IList<Color> colors)
{
if (colors == null) throw new ArgumentNullException("colors");
if (colors.Count > 256) throw new ArgumentOutOfRangeException("colors", "More than 256 colors");
int size = colors.Count;
for (int j = colors.Count; j < 256; j++)
{
colors.Add(Colors.White);
}
var palette = new BitmapPalette(colors);
byte[] pixels = new byte[size];
for (int i = 0; i < size; i++)
{
pixels[i] = (byte)i;
}
var bm = BitmapSource.Create(size, 1, 96, 96, PixelFormats.Indexed8, palette, pixels, 1 * size);
bm.Freeze();
return bm;
}
We also go to some effort to cache and reuse the internal Geometry structure when updating the point cloud.
Finally we display this with the awesome Helix Toolkit.

Categories

Resources