Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement hatch entity #36

Closed
dotoritos-kim opened this issue Mar 20, 2023 · 61 comments
Closed

Implement hatch entity #36

dotoritos-kim opened this issue Mar 20, 2023 · 61 comments

Comments

@dotoritos-kim
Copy link
Contributor

dotoritos-kim commented Mar 20, 2023

Hello, I'd like to implement the parser and renderer for hatch entity. Since this feature is essential to the CAD user, IMO it's important and valuable to proceed. My final goal is implementing a rendering logic for hatch, but to do so I have to parse hatch entity first.

If someone has any plan to do so (or even currently doing it), let's talk about more so that I can help and elaborate. If it's not, may I proceed to implement such feature? @vagran

Milestones

TBD

Reference

@dotoritos-kim dotoritos-kim changed the title Implement hatch entity Implement hatch entity Mar 20, 2023
@vagran
Copy link
Owner

vagran commented Mar 20, 2023

Hello,
Yes this feature is quite useful indeed. But it is also quite challenging to implement, and this is the reason why it is not implemented yet. Parsing hathing entity parameters is trivial, however rendering is not.
AFAIK nobody is implementing it now. But before proceding with your implementation, it indeed should be discussed about your vision or ideas of the implementation. Which technique do you plan to use for hatch rendering? It should be performant enough, not to slow down huge DXFs rendering.

@dotoritos-kim
Copy link
Contributor Author

dotoritos-kim commented Mar 20, 2023

Which technique do you plan to use for hatch rendering? It should be performant enough, not to slow down huge DXFs rendering.

My first suggestion of approach is using Material for predefined hatching pattern. Within my comprehension, Material is kind of textures. By using this approach, all I have to do is implement the creation of material with given specification. Any translation and transformation will be done on three.js side.

I found that there is a Custom Shader Material third party. I'm not sure this helps us or not, but at least there is a chance to be so. This might helps us define custom textures.

it indeed should be discussed about your vision or ideas of the implementation

Still more research is required for other possibilities. For example, more procedural options (see this) might be computational challenging. But I think these kind of hatching is less urgent than predefined things. I'm not sure whether it it possible or desirable to implement 100% spec-covering, since this library doesn't seem to aim for making a general CAD editor. For such thing, we need to discuss the direction of developments.

@vagran
Copy link
Owner

vagran commented Mar 21, 2023

Material is just a set of parameters used when rendering any 3D primitive. Currently dxf-viewer uses custom shaders (not that plugin, just own shaders) to render entities in most optimal way (for example, saving render buffers space by working with 2D coordinates only). There is a way to render a hatch as a texture. In such case you need to pre-render hatch texture tile (which should be able to be seamleassly repeated in both vertical and horizontal directions), then render a polygon filled with this texture. The polygon shape may be quite complex, with inner loops defined by hatch boundaries or external referenced entities. There is a drawback in this solution - when the drawing is scaled up, limited resolution of the hatch texture will be visible. Using signed distance field texture may improve the situation. However, as I see in Autodesk viewer, hatch pattern lines remain fixed width when scaling, thus meaning they are rendered as lines primitives. This approach is challenging in terms of calculating hatch pattern final shape after clipping with the hatch boundaries. Also resulting primitives should be rendered in most optimal way, not to cause explosive increase of used draw calls on a big DXF with a lot of hatched areas.
image
image

@dotoritos-kim
Copy link
Contributor Author

dotoritos-kim commented Mar 21, 2023

Thank you for your detailed explanation. I want to provide some of my thoughts with respect to your observations and other references:

  1. To see your exaplanation, using texture seems to be easy to implement, but may cause blurry looking. Therefore if I could I'll not use such strategy.
  2. I'll stick to the one using line primitives.
let p: [0, 1] → R^2 be the closed curve defined by Boundary Path Data, where p(0) = p(1). (Loop)
let P ⊆ R^2 be the closed bounded set where bd(P) = { p(t): t ∈ [0, 1] }
let f: [0, 1] → R^2 be any (partial) curve for the hatch pattern.
let F = { f(t): t ∈ [0, 1] }

Find continues curves g1, g2, ..., gk where 
  ∀t∈[0, 1], g(t) ∈ P ∩ F and 
  ∪gk([0, 1]) = P ∩ F

What I have to do is just find some interval(s) in [0, 1], for each interval I, f(I) is in P. For the simplicity, Let's fix f is restricted to a single straight line, at least for right now. (scaling up is trivial, and within my comprehension, every (hatch) pattern is set of lines)

image

Computing strategy to find such intersecting point should be differ to the property of P.

P can be split into segments differently with boundary path type flag and edge type.

  • Line is the easiest one, it's just midschool math.
  • Polyline is just set of lines.
  • Circular and Elliptic curve is pretty annoying but it can be explicitly computed some how.
  • Spline is the most hardest one since there is a degree parameter which makes computation horrible. It takes O(degree^2) to compute the exact point for arbitrary t. And more worse thing is, there is no direct explicit form for arbitrary degree, but non linear recursive form only- so I can't solve analytically. For this case I have to use numerical approach.

If there are N lines for f and M segments for p, then exactly NM solving process is required. I don't have further optimization idea for just right now to drop this complexity.

@dotoritos-kim
Copy link
Contributor Author

dotoritos-kim commented Mar 22, 2023

Simple Algorithm Showcase for Polyline

  • Analytically compute every intersection points, and then determine from which point to start
  • Even-Odd Rule to determine whether start point is inside
  • Alternates from start point to end for each intersection points on pattern line to be included in such set or not.
  • It's possible to optimize using kd-tree by quering only required sides of polygon- but note that this should be done with huge boundary path length.

https://editor.p5js.org/Phryxia/sketches/LleM_jJ1h

Animation

@vagran
Copy link
Owner

vagran commented Mar 24, 2023

OK, this might work.
One more thing to keep in mind: island detection. Boundaries may be nested each to other (with arbitrary big nesting depth), so only areas between them should be hatched. Boundaries intersection should be handled in some way as well. Probably the work should be started by creating some good test file with all these cases represented, to compare its rendering in some reference viewer (e.g. Autodesk online viewer) and dxf-viewer future implementation. Embedded boundaries vs boundaries defined by external entities should be covered as well.
BTW spline support should not be a problem, since in all cases you need to compute intersection not with the original curve, but with its tesselation result which is used for final rendering, i.e. baiscally with some polyline which is currently produced by arcs, circles, ellipses and splines.

@dotoritos-kim
Copy link
Contributor Author

dotoritos-kim commented Mar 25, 2023

since in all cases you need to compute intersection not with the original curve, but with its tesselation result which is used for final rendering, i.e. baiscally with some polyline which is currently produced by arcs, circles, ellipses and splines.

pretty brilliant!

One more thing to keep in mind: island detection. Boundaries may be nested each to other (with arbitrary big nesting depth), so only areas between them should be hatched.

I thought this might be hard but it wasn't. It was trivial than I expected. Whether polylines are nested or not doesn't affect to the algorithm. All I have to do is just count the intersections and determine the start point is interior.

Note that degenerate cases are ambiguous whether it's truley outside or inside. Nevertheless it works fine with even-odd rule, by not letting adjacent surfaces being not both inside or outside.

https://editor.p5js.org/Phryxia/sketches/LleM_jJ1h

image

But still there is buggy behavior as you can see above- edge case for the vertex. If line crosses path's vertex (or intersection of two path for degenerate cases) exactly, it is counted as twice currently. This screws up entire even-odd rule. To handle this I should think about handling near vertex case.

image

@dotoritos-kim
Copy link
Contributor Author

dotoritos-kim commented Mar 25, 2023

After few tweaking, I realize that hazard one is vertex-case only. Degenerate or wrapping path doesn't matter since it can be thought of separated area.

image

And vertex case can be detected easily, because solving linear equation of two line return two interpolation value t1 and t2. While t1 is used to cut the pattern, t2 can be used to detect whetherwhere intersection is occur one of two vertices. With tracking the duplication, this can be elliminated.

image

This logic is applied to above demo link

@vagran
Copy link
Owner

vagran commented Mar 25, 2023

That's interesting findings. What if you would account intersection with line only in range t [0; 1), thus including 0 and excluding 1? So in case of shared vertex it will be accounted only for the second line, not the first one.

@dotoritos-kim
Copy link
Contributor Author

What if you would account intersection with line only in range t [0; 1), thus including 0 and excluding 1?

This is why I love to cooperate for ideation. You're right, I confirmed that such approach is theoretically and practically works perfectly, and it's performance-healthy as duplication check is not required.

image

@dotoritos-kim
Copy link
Contributor Author

dotoritos-kim commented Mar 25, 2023

And the last small detail: Defining area (or positions) to generate pattern is just bounding box of given geometry, which can be obtained within O(n) time complexity, for n boundary path length.

@vagran
Copy link
Owner

vagran commented Mar 25, 2023

OK, looks like things are getting serious, so there is no way back anymore :) Let's plan the work.

First, we should define the limited scope.

  1. I think for initial implementation we can support only pattern fill (group 70=0), solid fill will be unimplemented for now.
  2. No support for MPolygon.
  3. Group 75 (Hatch style). I think value 1 can be left unsupported for now (Hatch outermost area only (Outer style)). It looks untrivially to support it since (if I correctly understand its meaning), we need to calculate boundary based on surrounding entities which may be heavy-weight task, and it might require some spatial index to make it work on big files. 0 (Hatch “odd parity” area) is basically what you have just researched. Value 2 (Hatch through entire area) does not look like is a complex task. If I correctly understand, we need to calculate some union envelope shape for all boundaries, and than apply hatch lines intersection algorithm to the resulting shape.
  4. Group 76 hatch pattern type. I did not really get what is the difference between 0 = User-defined and 2 = Custom. We need to support the one which provides pattern definition in the HATCH entity. Type 1 = Predefined should be supported just conceptually - we will define a couple of built-in (most simple and widely used) patterns from this list.
  5. Group 77 Hatch pattern double flag - I am unsure what it does (lines are doubled?). Probably we can ignore it initially, and draw everything not doubled.
  6. I did not really get what "seed point" means. Sounds like origin for pattern instantiation, but why they are multiple?
  7. Group 92 Boundary path type flag - support 0 = Default; 1 = External; 2 = Polyline. The rest seems to be less common and less triavial to implement.
  8. Group 72 Edge type - all of them (1 = Line; 2 = Circular arc; 3 = Elliptic arc; 4 = Spline).
  9. Everything for pattern definition.

Overall milestones to do in dxf-viewer code and architecture design:

  1. We need to implement HATCH entity parsing into a convenient form.
  2. We need to refactor DxfScene entities decomposition code so that tesselated curves can be obtained for a given entity (either existing entity in DXF for external boundary reference, or by circle/ellipse/spline definition in HATCH entity. They should be in OCS coordinates before any transformations are applied.
  3. We need to create an index for all DXF entities so that quick lookup by handle value can be done (required for external boundary references). I am not sure if block instance referencing is possible. Probably should leave unsupported it initially.
  4. Module for calculating pattern fill (based on your research).
  5. HATCH entity decomposition handler based on all above components.

Actual tasks for current stage:

  1. We need sample files which demonstrate most of features we want to cover, and any corner cases we know about (like this one with vertex intersection). Probably can be created in some software like QCAD, with manual inspection of resulting DXF in text editor to confirm it contains necessary features. May be some manual editing of DXF produced by CAD software, if the software is not capable enough. We need variuos kinds of boundaries, with various edge types, nestings and instersections, external entities references. We need variety of used hatch styles (odd parity/though entire area). We need user-defined patterns with clearly identifiable origin point, several variations of scale and rotation. Some supported built-in pattern example. Target is to ensure such samples are properly displayed in the reference viewer and make the same result in the dxf-viewer.
  2. Math research should be continued to get solution also for Hatch style 2 (Hatch through entire area) mentioned above. We need to calculate polyline for union of several overallapping boundaries. In case some of them are isolated we should get several loops as a result. Also it would be nice to check if it is a proper definition of what should be done, by creating sample file with such kind of hatch style and several overlapping and non-overlapping loops and checking how they are displayed in the Autodesk viewer.
  3. When we have all the algorithms prototyped the actual code can be created. I propose to isolate it in a separate file with a clean and simple interface. In my vision, I see the following interface:
    • A class incapsulating the calculator state and providing methods to get pattern definition area, and to clip a particular pattern line segment (resulting zero or many resulting segments to draw). Describing it in TypeScript:
    type Point = {x: number, y: number}
    type Loop = Point[]
    type LineSegment = Point[] // two elements, start and end
    type BBox = {min: Point, max: Point}
    
    class PatternFillCalculator {
        constructor(loops: Loop[])
    
        GetPatternBounds(base: Point, angle: number, scale: number): BBox
    
        ClipLine(line: LineSegment): LineSegment[]
    }
    Pattern bounds should be returned in pattern local coordinates (pattern starts in [0; 0] oriented horizontally, scale 1). So that pattern synthesizer code could iterate all rows of the pattern in the returned region, and clip each individual line (keep in mind that pattern row may be not only a single line, but some complex shape as well, see some predefined patterns. The synthesizer will break such pattern into individual line segments, and will clip each one separately.

I have created dev-hatch branch in dxf-viewer repository, so that I could share my effort as well, and we could synchronize the development. Just create a branch in your repository based on this branch, and merge it there periodically (git rebase is also a good thing to use to rebase your changes on top of it). Remember that this branch should not contain any unrelated changes to make it possible to get a clean PR later. I will probably start with the parser stuff.

@dotoritos-kim
Copy link
Contributor Author

dotoritos-kim commented Mar 26, 2023

Seems to be a long journey. As I haven't inspect the entire code base, few parts of your explanation seems unclear for right now but by proceeding from small implementation I think I can get there soon.

First I'll proceed to implement parser. And here a very simple hatch example generated by qcad.

simple.zip

This has seven hatchs defined by external. (QCAD can only create a hatch by external as my understanding) Top left one is polyline. More complex example (more than 1 depth nesting, varied edge types...) has to be done manually. Note that line-looking one with right bottom one is degenerate case. Image is rendered by reference viewer.

image

By the way, do you have any plan to introduce typescript to this repo? If you're busy, I can help you that first. This will help everyone develop more easily, with rich support for IDE and more safe code behavior. I have experienced (for example) handling lots of configuration file related to repository. (Note that I'm actually the author of that, but for some private reason I'm using coworker's account)

@vagran
Copy link
Owner

vagran commented Mar 26, 2023

By the way, do you have any plan to introduce typescript to this repo?

Yes, it is in my todo list indeed. It looks quite trivial to migrate the core code to it. However, I had a plan to significantly rewrite the parser simultanenously with this migration. Introducing strong typing into its current implementation (which is forked 3rd-party component) seems is not straight forward. Also it would be nice to change overall approach used in the parser, to make it true streaming parser. Currently it loads all the DXF text content fully in the memory as a single string, which limits the possible DXF size to ~1GB, then browser JS string size limit is reached. Also memory consumption equal to file size is not necessary. I have DXF sample which has big embedded OLE object, which we could easily ignore and display the rest content, but it cannot be loaded due to this unnecessary bufferring.

If you want, I can start this migration before proceeding with hatching implementation. However, I do not see real possibility to parallel this work. Describing all the types in DXF viewer is a task for me, since I am the only person knowing this in details. Some advices on configuration and overall layout might be welcome indeed.

If we proceed with this way, I would advice you to continue on hatching task, since it can be effectively paralleled. Having full implementation of PatternFillCalculator would be nice to have when hatching implementation starts. You may also think about some testing for it. Currently dxf-viewer does not have any testing framework at all. It would be nice to introduce something, and start covering core logic with some kind of tests.

BTW constructor(loops: Loop[]) in the interface definition above should also accept parameter for hatch style (odd parity / entire area).

@dotoritos-kim
Copy link
Contributor Author

dotoritos-kim commented Mar 26, 2023

Introducing strong typing into its current implementation (which is forked 3rd-party component) seems is not straight forward

Oh I see. I think it's very important issue. So let's make typescript to wait for refactoring.

If you want, I can start this migration before proceeding with hatching implementation. However, I do not see real possibility to parallel this work.

Since I have some deadline for my contraction, it's hard to wait until the migration. I'm also pessimiste to parallel work- especially current commit policy: squash merge. Note that sqaush and even rebase merge, conflicts a lot. (Not rebasing my branch but it matters when merging PR on github) This is because git's conflict detection is based on finding common ancestor and compare them, and squash & rebase merge has only one parent. It's a matter of preference, but obviously parallel working and squashing often fight each other as my experience at company's works.

Therefore, I think implementing hatch ASAP seems to be better option.

Currently dxf-viewer does not have any testing framework at all. It would be nice to introduce something, and start covering core logic with some kind of tests.

I definetely agree. I've used jest and it was reliable, and was easy to use for unit test. Preparing test environment doesn't introduce breaking changes, so I'd happy to do so. This state of js chart would make you help.

@vagran
Copy link
Owner

vagran commented Mar 26, 2023

OK, let's postpone TypeScript and focus on hatching now. I still propose you to get PatternFillCalculator done, and I will prepare dxf-viewer for hatching implementation (milestones 1,2,3 from my message with action plan).

@vagran
Copy link
Owner

vagran commented Mar 26, 2023

I'm also pessimiste to parallel work- especially current commit policy: squash merge. Note that sqaush and even rebase merge, conflicts a lot.

Actually conflicts occurs independently from using or not using rebase, if the same place is changed simultaneously in different branches, you will get conflict anyway. You can use any merge policy you want, I can handle the final reintegration to master branch to make the history linear. Just need to ensure that our changes are based on the same branch (dev-hatch is created for that in this repo).

@dotoritos-kim
Copy link
Contributor Author

dotoritos-kim commented Mar 26, 2023

I still propose you to get PatternFillCalculator done

Oh, that's the priority. I'll go for it~ Can I introduce jest with above also?

And by the way, few hours before I sketched following parser structure so that I can get some meaningful data. (The time that I haven't thought about separating PatternFillCalculator) Can you consider this sturcture when you implement hatch parser? I've seen other parser file, but as there are duplicated group code on hatch references so it can't be applied to hatch parser. I think this might be helpful.

// declarative schema for each 
const Schema = [
  {
    // Subclass marker (=AcDbHatch)
    code: 100,
    name: 'subclassMarker',
    parse: (value, scanner) => value // value parma should be injected from scanner.next().value
  },
  // Elevation point (in OCS)
  {
    code: 10,
    name: 'elevationPoint',
    parse: (value, scanner) => helpers.parsePoint(scanner)
  },
  // Extrusion direction (optional)
  {
    code: 210,
    name: 'extrusionDirection',
    parse: (value, scanner) => helpers.parsePoint(scanner),
    fallback: { // for optional field, 
      x: 0,
      y: 0,
      z: 1,
    }
  },
  // ... 
}

// common logic (pseudocode)
let sptr = 0
let curr = scanner.next()
while not eof:
  const schema = Schema[sptr++]
  const entity = {}
  
  if schema.code  curr.code:
    if !schema.fallback: throw error
    entity[schema.name] = fallback
    continue

  entity[schema.name] = schema.parse(curr.value, scanner)
  curr = scanner.next()

@vagran
Copy link
Owner

vagran commented Mar 26, 2023

Can I introduce jest with above also?

Sure. It would be nice to start some movement to this direction. jest looks good, seems like it is state-of-the-art for JS unit testing.

Can you consider this sturcture when you implement hatch parser?

Declarative approach is a way to go, indeed. I will consider something similar when will make a rewrite of a parser. However, currently it may be better to follow existing code look-and-fill. Another quick notes on your code - it assumes some specific groups order, I am not sure if it is always the case for real-world files. I did not find this requirement in DXF specification, it may be asserted for some sequences like 10,20,30 for 3D coordinates, but not really clear about overall order in an entity. There might be different software which produces DXF, and the viewer should be kept as compatible as possible, and tolerate possible misordering. Another issue is improper subclassMarker handling. This marker occurs several times in a single entity and separates virtual class hierarchy for that entity. E.g. entity typically starts by subclass AcDbEntity, which is base class, for all entities, followed by common groups for all entities, like handle, layer, color, then next more specific subclass, e.g. AcDbDimension followed by fields from this subclass, then even more specific one, e.g. AcDbAlignedDimension, then AcDbRotatedDimension and so on. Theoretically the parser should maintain current subclass name when parsing entity, and it might interpret same group codes differently depending on context. However current implementation does not do anything similar, just relying all codes have their dedicated meaning in current entity context, also being tolerant to groups misordering. BTW elevationPoint does not seem to be required in dxf viewer, according to the description it is some elevation Z value specified, which is not necessary for 2D viewer.

@dotoritos-kim
Copy link
Contributor Author

dotoritos-kim commented Mar 26, 2023

Sure. It would be nice to start some movement to this direction. jest looks good, seems like it is state-of-the-art for JS unit testing.

Thanks I'll do it.

I did not find this requirement in DXF specification, it may be asserted for some sequences like 10,20,30 for 3D coordinates, but not really clear about overall order in an entity.

I even found saying DO NOT rely on order that here. Therefore you're right. But I wonder the case of undeterminable case like hatch. For such cases should be handled using little bit order. But it seems that they are somehow split by further subclass.

Another issue is improper subclassMarker handling. This marker occurs several times in a single entity and separates virtual class hierarchy for that entity.

Just forget about this- I misunderstood the specification group code.

BTW elevationPoint does not seem to be required in dxf viewer, according to the description it is some elevation Z value specified, which is not necessary for 2D viewer.

Yeah, if you worries about redundant memories to hold such field, they must be commented out.


so I think current implementation may parse correctly with few modification. (Though I haven't inspect all entities, and also I don't have sense to detect which I should ignore or not) Anyway for hatch, following should be considered.

100 | Subclass marker (AcDbHatch)
10 | Elevation point (in OCS)
...
10 | Seed point (in OCS)

And I'll just concentrate on calculator, at least for now. (and jest)

@vagran
Copy link
Owner

vagran commented Mar 26, 2023

Did you get, what does this "seed point" mean? Why there can be multiple seed points specified? I did not find any information on that, my current assumption is that it defines hatch pattern instantiation origin coordinate. And multiple of them means that the pattern can be instantiated multiple times in one HATCH entity with different offset applied. This is the thing which should be checked as well by creating corresponding sample file and checking it in the reference viewer. Then "base point" in a pattern definition line should be somehow defined as well. I currently assume it is its anchor point which corresponds to seed point when instantiated, just like with BLOCK (base point there) and INSERT (insertion point there).

Just for fun I have asked ChatGPT about that, and it answered exactly the same as I assumed, however you might know that information it provides is not yet considered to be reliable, and it does not provide any source references for that. ChatGPT_dxf_hatch_seed_points.pdf

And yes, this is exactly the case, when subclass markers should be considered and should affect current parsing context. For now, it is quite trivially to remember last subclass encountered and interpret group 10 accordingly. In the future, I will implement some declarative approach, when the parser will be rewritten.

@dotoritos-kim
Copy link
Contributor Author

dotoritos-kim commented Mar 26, 2023

@vagran

Did you get, what does this "seed point" mean?

I think I found the right one... but I can't understand what is exactly means as I'm not expert on using CAD... Can you explain to me this docs more easier?

https://help.autodesk.com/view/ASMECH/2016/ENU/?guid=GUID-1C7B56A1-D396-4A28-9A6F-51C07438B46E

dotoritos-kim added a commit to dotoritos-kim/dxf-viewer that referenced this issue Mar 26, 2023
dotoritos-kim added a commit to dotoritos-kim/dxf-viewer that referenced this issue Mar 26, 2023
dotoritos-kim added a commit to dotoritos-kim/dxf-viewer that referenced this issue Mar 26, 2023
@vagran
Copy link
Owner

vagran commented Mar 26, 2023

@vagran

Did you get, what does this "seed point" mean?

I think I found the right one... but I can't understand what is exactly means as I'm not expert on using CAD... Can you explain to me this docs more easier?

https://help.autodesk.com/view/ASMECH/2016/ENU/?guid=GUID-1C7B56A1-D396-4A28-9A6F-51C07438B46E

Seems this doc is not related to hatch seed points, looks like completely different topic. But I think my assumptions described in my previous message look believable enough, just need to check them by making a sample file.

vagran added a commit that referenced this issue Mar 27, 2023
@vagran
Copy link
Owner

vagran commented Mar 27, 2023

I have implemented basic parsing for HATCH entity. I did not finish spline boundary type parsing, will do it later.

I noticed that QCAD always creates pattern as pre-defined, referencing it by name, even for completely custom patterns like QCAD logo. It defines simple line as a fallback, so it is always displayed as simple line hatch in the reference viewer. Also was unable to find example files with custom pattern. Probably will need to somehow synthesise one. Actually this also is valid for most of other hatch features.

By the way, if the solid fill will be supported in the future, seems the current approach will need to be redesigned. Solid infill will need polygon definition (probably in form of polygon with holes structure). Thus the algorithm will need to generate such polygons. Then it will probably be more efficient to clip the pattern lines by these polygons.

@vagran
Copy link
Owner

vagran commented Apr 12, 2023

I'd like to claim strongly that we don't have to support such illegal polygons concisely. They are so abnormal, so that there is no reason to put such polygons in production purpose CAD file.

Yes, that's true, however, when you release some product in real world, you will be very surprised about variety of use cases users will encounter. All unhandled edge cases will return back in opened issues, with examples which somehow handled in other software and are improperly displayed in dxf-viewer. So it should be done as robust as possible.

I also thought about it, and have some ideas to make it rock-solid. All this instability should not be a problem. First, I initially planned to detect separately colinear edges and do not try to calculate intersections with them, but use them to clip resulted segments. This was net yet implemented, and I think it will not be a problem to detect colinearity with some threshold by some method, like one you have described. Another thing I understood during debugging, is a way to properly handle such cases:
image
Because of this instability, it might happen, that intersection is detected with edge A and not edge B (or wise versa), i.e. line parameter for A may be 0.999999, and for B -0.00001. Or even with neither of such edges (e.g. 1.0001 for A and -0.0001 for B):
image

That is what I have seen in my examples. The solution is, first, allow some margin on acceptable parameter range (out of [0; 1]) and, second, to force intersection calculation for connected edge if there was intersection near edge endpoint. And then apply side comparison logic - in case it intersects both edges from one side, it is state toggling, otherwise no toggling (or toggle on very small distance, which is then filtered out). The edge may be either directly connected or via one or more colinear segments, same logic applies, ignoring interim colinear segment(s).
image

image

@vagran
Copy link
Owner

vagran commented Apr 12, 2023

Unless we use verified academic method like I posted #36 (comment), it's akward to support such edge case.

Once it is done and proved to be working, one can write a paper about this method and make it academic 😁

@vagran
Copy link
Owner

vagran commented Apr 12, 2023

By the way, I'll implement some patterns in this list.

I have seen some mentions about *.pat files used in CAD software to define these patterns. May be it is worth to extract definitions from them with some script, if it is possible to get these files from some software?

@vagran
Copy link
Owner

vagran commented Apr 14, 2023

I just finished implementation of that algorithm with all fixes. Currently seems to be working flawlessly on all my test samples. Just need to finish "hatch-through" support as well, and adapt dashes rendering to new approach.

image
image
image
image
image
image
image
image
image
image
image

@vagran
Copy link
Owner

vagran commented Apr 14, 2023

Implemented "THOUGH_ENTIRE_AREA" clipping mode. Works for all above samples as well.
image

vagran added a commit that referenced this issue Apr 14, 2023
vagran added a commit that referenced this issue Apr 14, 2023
vagran added a commit that referenced this issue Apr 14, 2023
@vagran
Copy link
Owner

vagran commented Apr 14, 2023

But what about custom patterns? There are many cases of non-linear structure like these

Before I started to draw this picture, I was pretty sure, that custom patterns are represented in the same way, just each separate line (marked by it own color on the picture) has its own entry in the pattern definition, with its own angle, offset, and dash length

According to AutoCAD docs (1, 2, 3), I was pretty close to the truth. Patterns in *.pat files are represented as I described, and support for such structure is already implemented in my code. Just need some small adaptor code to parse just pasted numbers sequence from *.pat files, then find somewhere these files for a number of built-in patterns, and add them to the code.

BTW just implemented dashes support. Hatch clipping seems to be complete.

@vagran
Copy link
Owner

vagran commented Apr 14, 2023

Here are pat files in QCAD project.

@vagran
Copy link
Owner

vagran commented Apr 15, 2023

Just added support for custom patterns imported from .pat files (see src/patterns directory).
image
image

vagran added a commit that referenced this issue Apr 16, 2023
vagran added a commit that referenced this issue Apr 19, 2023
vagran added a commit that referenced this issue Apr 19, 2023
vagran added a commit that referenced this issue Apr 19, 2023
@vagran
Copy link
Owner

vagran commented Apr 19, 2023

Almost finished the initial roadmap for this feature. Just added spline support. One more fun fact: QCAD is not able to display spline hatch it produces.
image
image
spline.dxf.zip

The only thing I currently see which stops merging this feature in master is suspicious behaviour on this file:
AEC Plan Elev Sample (dim,hatch).dxf.zip

Autodesk:
image
dxf-viewer:
image
Scale is obviously not matching, and gravel pattern line is somehow displaced relative to boundaries. May be this is floating point number precision loss, and operations order should be revised.

@vagran
Copy link
Owner

vagran commented Apr 19, 2023

Scale mismatch is caused by using metric patterns on drawing with imperial units. Will probably need to make two sets of pattern definitions like in QCAD. Problem with boundaries is now more visible, does not look as precision lost.

image

@vagran
Copy link
Owner

vagran commented Apr 19, 2023

Scale mismatch is caused by using metric patterns on drawing with imperial units. Will probably need to make two sets of pattern definitions like in QCAD. Problem with boundaries is now more visible, does not look as precision lost.

The problem was in bulged polyline segments handling, fixed. So after implementation of proper handling for metric/imperial patterns this feature can be released, if no more problems found.

vagran added a commit that referenced this issue Apr 20, 2023
@vagran
Copy link
Owner

vagran commented Apr 20, 2023

Just have finished separate patterns for imperial and metric. Everything is looking good now. The feature is merged into master and will be included in the nearest release (currently blocked by #51).

@dlabz
Copy link

dlabz commented Jun 26, 2023

This is awesome work!

Last time I checked, even Autodesk Forge Viewer rendered hatches serverside and transfered them as disconnected line segments, which usually resulted in hatches being a huge part of their .f2d files.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants